1 // Copyright (c) the JPEG XL Project Authors. All rights reserved.
3 // Use of this source code is governed by a BSD-style
4 // license that can be found in the LICENSE file.
5 #include "tools/benchmark/benchmark_codec_jpeg.h"
13 #include <numeric> // partial_sum
16 #if JPEGXL_ENABLE_JPEGLI
17 #include "lib/extras/dec/jpegli.h"
19 #include "lib/extras/dec/jpg.h"
20 #if JPEGXL_ENABLE_JPEGLI
21 #include "lib/extras/enc/jpegli.h"
23 #include "lib/extras/enc/jpg.h"
24 #include "lib/extras/packed_image.h"
25 #include "lib/extras/packed_image_convert.h"
26 #include "lib/extras/time.h"
27 #include "lib/jxl/base/span.h"
28 #include "lib/jxl/codec_in_out.h"
29 #include "lib/jxl/image_bundle.h"
30 #include "tools/benchmark/benchmark_utils.h"
31 #include "tools/cmdline.h"
32 #include "tools/file_io.h"
33 #include "tools/thread_pool_internal.h"
39 std::string base_quant_fn;
46 float search_tolerance;
47 float search_q_precision;
48 float search_first_iter_slope;
51 static JPEGArgs* const jpegargs = new JPEGArgs;
53 #define SET_ENCODER_ARG(name) \
54 if (jpegargs->name > 0) { \
55 encoder->SetOption(#name, std::to_string(jpegargs->name)); \
58 Status AddCommandLineOptionsJPEGCodec(BenchmarkArgs* args) {
59 args->AddString(&jpegargs->base_quant_fn, "qtables",
60 "Custom base quantization tables.");
61 args->AddFloat(&jpegargs->search_q_start, "search_q_start",
62 "Starting quality for quality-to-target search", 0.0f);
63 args->AddFloat(&jpegargs->search_q_min, "search_q_min",
64 "Minimum quality for quality-to-target search", 0.0f);
65 args->AddFloat(&jpegargs->search_q_max, "search_q_max",
66 "Maximum quality for quality-to-target search", 0.0f);
67 args->AddFloat(&jpegargs->search_d_min, "search_d_min",
68 "Minimum distance for quality-to-target search", 0.0f);
69 args->AddFloat(&jpegargs->search_d_max, "search_d_max",
70 "Maximum distance for quality-to-target search", 0.0f);
71 args->AddFloat(&jpegargs->search_tolerance, "search_tolerance",
72 "Percentage value, if quality-to-target search result "
73 "relative error is within this, search stops.",
75 args->AddFloat(&jpegargs->search_q_precision, "search_q_precision",
76 "If last quality change in quality-to-target search is "
77 "within this value, search stops.",
79 args->AddFloat(&jpegargs->search_first_iter_slope, "search_first_iter_slope",
80 "Slope of first extrapolation step in quality-to-target "
83 args->AddSigned(&jpegargs->search_max_iters, "search_max_iters",
84 "Maximum search steps in quality-to-target search.", 0);
88 class JPEGCodec : public ImageCodec {
90 explicit JPEGCodec(const BenchmarkArgs& args) : ImageCodec(args) {}
92 Status ParseParam(const std::string& param) override {
93 if (param[0] == 'q' && ImageCodec::ParseParam(param)) {
94 enc_quality_set_ = true;
97 if (ImageCodec::ParseParam(param)) {
100 if (param == "sjpeg" || param.find("cjpeg") != std::string::npos) {
101 jpeg_encoder_ = param;
104 #if JPEGXL_ENABLE_JPEGLI
105 if (param == "enc-jpegli") {
106 jpeg_encoder_ = "jpegli";
110 if (param.compare(0, 3, "yuv") == 0) {
111 chroma_subsampling_ = param.substr(3);
114 if (param.compare(0, 4, "psnr") == 0) {
115 psnr_target_ = std::stof(param.substr(4));
118 if (param[0] == 'p') {
119 progressive_id_ = strtol(param.substr(1).c_str(), nullptr, 10);
122 if (param == "fix") {
126 if (param[0] == 'Q') {
127 libjpeg_quality_ = strtol(param.substr(1).c_str(), nullptr, 10);
130 if (param.compare(0, 3, "YUV") == 0) {
131 if (param.size() != 6) return false;
132 libjpeg_chroma_subsampling_ = param.substr(3);
135 if (param == "noaq") {
136 enable_adaptive_quant_ = false;
139 #if JPEGXL_ENABLE_JPEGLI
140 if (param == "xyb") {
144 if (param == "std") {
145 use_std_tables_ = true;
148 if (param == "dec-jpegli") {
149 jpeg_decoder_ = "jpegli";
152 if (param.substr(0, 2) == "bd") {
153 bitdepth_ = strtol(param.substr(2).c_str(), nullptr, 10);
156 if (param.substr(0, 6) == "cquant") {
157 num_colors_ = strtol(param.substr(6).c_str(), nullptr, 10);
164 bool IgnoreAlpha() const override { return true; }
166 Status Compress(const std::string& filename, const CodecInOut* io,
167 ThreadPool* pool, std::vector<uint8_t>* compressed,
168 jpegxl::tools::SpeedStats* speed_stats) override {
169 if (jpeg_encoder_.find("cjpeg") != std::string::npos) {
170 // Not supported on Windows due to Linux-specific functions.
171 // Not supported in Android NDK before API 28.
172 #if !defined(_WIN32) && !defined(__EMSCRIPTEN__) && \
173 (!defined(__ANDROID_API__) || __ANDROID_API__ >= 28)
174 const std::string basename = GetBaseName(filename);
175 TemporaryFile in_file(basename, "pnm");
176 TemporaryFile encoded_file(basename, "jpg");
177 std::string in_filename, encoded_filename;
178 JXL_RETURN_IF_ERROR(in_file.GetFileName(&in_filename));
179 JXL_RETURN_IF_ERROR(encoded_file.GetFileName(&encoded_filename));
180 const size_t bits = io->metadata.m.bit_depth.bits_per_sample;
181 ColorEncoding c_enc = io->Main().c_current();
182 std::vector<uint8_t> encoded;
184 Encode(*io, c_enc, bits, in_filename, &encoded, pool));
185 JXL_RETURN_IF_ERROR(WriteFile(in_filename, encoded));
186 std::string compress_command = jpeg_encoder_;
187 std::vector<std::string> arguments;
188 arguments.push_back("-outfile");
189 arguments.push_back(encoded_filename);
190 arguments.push_back("-quality");
191 arguments.push_back(std::to_string(static_cast<int>(q_target_)));
192 arguments.push_back("-sample");
193 if (chroma_subsampling_ == "444") {
194 arguments.push_back("1x1");
195 } else if (chroma_subsampling_ == "420") {
196 arguments.push_back("2x2");
197 } else if (!chroma_subsampling_.empty()) {
198 return JXL_FAILURE("Unsupported chroma subsampling");
200 arguments.push_back("-optimize");
201 arguments.push_back(in_filename);
202 const double start = jxl::Now();
203 JXL_RETURN_IF_ERROR(RunCommand(compress_command, arguments, false));
204 const double end = jxl::Now();
205 speed_stats->NotifyElapsed(end - start);
206 return ReadFile(encoded_filename, compressed);
208 return JXL_FAILURE("Not supported on this build");
212 jxl::extras::PackedPixelFile ppf;
213 size_t bits_per_sample = io->metadata.m.bit_depth.bits_per_sample;
214 JxlPixelFormat format = {
215 0, // num_channels is ignored by the converter
216 bits_per_sample <= 8 ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16, JXL_BIG_ENDIAN,
218 JXL_RETURN_IF_ERROR(ConvertCodecInOutToPackedPixelFile(
219 *io, format, io->metadata.m.color_encoding, pool, &ppf));
220 double elapsed = 0.0;
221 if (jpeg_encoder_ == "jpegli") {
222 #if JPEGXL_ENABLE_JPEGLI
223 jxl::extras::JpegSettings settings;
224 settings.xyb = xyb_mode_;
226 settings.use_std_quant_tables = use_std_tables_;
228 if (enc_quality_set_) {
229 settings.quality = q_target_;
231 settings.distance = butteraugli_target_;
233 if (progressive_id_ >= 0) {
234 settings.progressive_level = progressive_id_;
236 if (psnr_target_ > 0) {
237 settings.psnr_target = psnr_target_;
239 if (jpegargs->search_tolerance > 0) {
240 settings.search_tolerance = 0.01f * jpegargs->search_tolerance;
242 if (jpegargs->search_d_min > 0) {
243 settings.min_distance = jpegargs->search_d_min;
245 if (jpegargs->search_d_max > 0) {
246 settings.max_distance = jpegargs->search_d_max;
248 settings.chroma_subsampling = chroma_subsampling_;
249 settings.use_adaptive_quantization = enable_adaptive_quant_;
250 settings.libjpeg_quality = libjpeg_quality_;
251 settings.libjpeg_chroma_subsampling = libjpeg_chroma_subsampling_;
252 settings.optimize_coding = !fix_codes_;
253 const double start = jxl::Now();
255 jxl::extras::EncodeJpeg(ppf, settings, pool, compressed));
256 const double end = jxl::Now();
257 elapsed = end - start;
260 jxl::extras::EncodedImage encoded;
261 std::unique_ptr<jxl::extras::Encoder> encoder =
262 jxl::extras::GetJPEGEncoder();
264 fprintf(stderr, "libjpeg codec is not supported\n");
267 std::ostringstream os;
268 os << static_cast<int>(std::round(q_target_));
269 encoder->SetOption("q", os.str());
270 encoder->SetOption("jpeg_encoder", jpeg_encoder_);
271 if (!chroma_subsampling_.empty()) {
272 encoder->SetOption("chroma_subsampling", chroma_subsampling_);
274 if (progressive_id_ >= 0) {
275 encoder->SetOption("progressive", std::to_string(progressive_id_));
277 if (libjpeg_quality_ > 0) {
278 encoder->SetOption("libjpeg_quality", std::to_string(libjpeg_quality_));
280 if (!libjpeg_chroma_subsampling_.empty()) {
281 encoder->SetOption("libjpeg_chroma_subsampling",
282 libjpeg_chroma_subsampling_);
285 encoder->SetOption("optimize", "OFF");
287 if (!enable_adaptive_quant_) {
288 encoder->SetOption("adaptive_q", "OFF");
290 if (psnr_target_ > 0) {
291 encoder->SetOption("psnr", std::to_string(psnr_target_));
293 if (!jpegargs->base_quant_fn.empty()) {
294 encoder->SetOption("base_quant_fn", jpegargs->base_quant_fn);
296 SET_ENCODER_ARG(search_q_start);
297 SET_ENCODER_ARG(search_q_min);
298 SET_ENCODER_ARG(search_q_max);
299 SET_ENCODER_ARG(search_q_precision);
300 SET_ENCODER_ARG(search_tolerance);
301 SET_ENCODER_ARG(search_first_iter_slope);
302 SET_ENCODER_ARG(search_max_iters);
303 const double start = jxl::Now();
304 JXL_RETURN_IF_ERROR(encoder->Encode(ppf, &encoded, pool));
305 const double end = jxl::Now();
306 elapsed = end - start;
307 *compressed = encoded.bitstreams.back();
309 speed_stats->NotifyElapsed(elapsed);
313 Status Decompress(const std::string& filename,
314 const Span<const uint8_t> compressed, ThreadPool* pool,
316 jpegxl::tools::SpeedStats* speed_stats) override {
317 jxl::extras::PackedPixelFile ppf;
318 if (jpeg_decoder_ == "jpegli") {
319 #if JPEGXL_ENABLE_JPEGLI
320 std::vector<uint8_t> jpeg_bytes(compressed.data(),
321 compressed.data() + compressed.size());
322 const double start = jxl::Now();
323 jxl::extras::JpegDecompressParams dparams;
324 dparams.output_data_type =
325 bitdepth_ > 8 ? JXL_TYPE_UINT16 : JXL_TYPE_UINT8;
326 dparams.num_colors = num_colors_;
328 jxl::extras::DecodeJpeg(jpeg_bytes, dparams, pool, &ppf));
329 const double end = jxl::Now();
330 speed_stats->NotifyElapsed(end - start);
333 const double start = jxl::Now();
334 jxl::extras::JPGDecompressParams dparams;
335 dparams.num_colors = num_colors_;
337 jxl::extras::DecodeImageJPG(compressed, jxl::extras::ColorHints(),
338 &ppf, /*constraints=*/nullptr, &dparams));
339 const double end = jxl::Now();
340 speed_stats->NotifyElapsed(end - start);
343 jxl::extras::ConvertPackedPixelFileToCodecInOut(ppf, pool, io));
348 // JPEG encoder and its parameters
349 std::string jpeg_encoder_ = "libjpeg";
350 std::string chroma_subsampling_;
351 int progressive_id_ = -1;
352 bool fix_codes_ = false;
353 float psnr_target_ = 0.0f;
354 bool enc_quality_set_ = false;
355 int libjpeg_quality_ = 0;
356 std::string libjpeg_chroma_subsampling_;
357 #if JPEGXL_ENABLE_JPEGLI
358 bool xyb_mode_ = false;
359 bool use_std_tables_ = false;
361 bool enable_adaptive_quant_ = true;
362 // JPEG decoder and its parameters
363 std::string jpeg_decoder_ = "libjpeg";
365 #if JPEGXL_ENABLE_JPEGLI
366 size_t bitdepth_ = 8;
370 ImageCodec* CreateNewJPEGCodec(const BenchmarkArgs& args) {
371 return new JPEGCodec(args);
375 } // namespace jpegxl