Imported Upstream version 0.9.0
[platform/upstream/libjxl.git] / tools / benchmark / benchmark_codec_jpeg.cc
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 #include "tools/benchmark/benchmark_codec_jpeg.h"
6
7 #include <stddef.h>
8 #include <stdio.h>
9 // After stddef/stdio
10 #include <stdint.h>
11 #include <string.h>
12
13 #include <numeric>  // partial_sum
14 #include <string>
15
16 #if JPEGXL_ENABLE_JPEGLI
17 #include "lib/extras/dec/jpegli.h"
18 #endif
19 #include "lib/extras/dec/jpg.h"
20 #if JPEGXL_ENABLE_JPEGLI
21 #include "lib/extras/enc/jpegli.h"
22 #endif
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"
34
35 namespace jpegxl {
36 namespace tools {
37
38 struct JPEGArgs {
39   std::string base_quant_fn;
40   float search_q_start;
41   float search_q_min;
42   float search_q_max;
43   float search_d_min;
44   float search_d_max;
45   int search_max_iters;
46   float search_tolerance;
47   float search_q_precision;
48   float search_first_iter_slope;
49 };
50
51 static JPEGArgs* const jpegargs = new JPEGArgs;
52
53 #define SET_ENCODER_ARG(name)                                  \
54   if (jpegargs->name > 0) {                                    \
55     encoder->SetOption(#name, std::to_string(jpegargs->name)); \
56   }
57
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.",
74                  0.0f);
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.",
78                  0.0f);
79   args->AddFloat(&jpegargs->search_first_iter_slope, "search_first_iter_slope",
80                  "Slope of first extrapolation step in quality-to-target "
81                  "search.",
82                  0.0f);
83   args->AddSigned(&jpegargs->search_max_iters, "search_max_iters",
84                   "Maximum search steps in quality-to-target search.", 0);
85   return true;
86 }
87
88 class JPEGCodec : public ImageCodec {
89  public:
90   explicit JPEGCodec(const BenchmarkArgs& args) : ImageCodec(args) {}
91
92   Status ParseParam(const std::string& param) override {
93     if (param[0] == 'q' && ImageCodec::ParseParam(param)) {
94       enc_quality_set_ = true;
95       return true;
96     }
97     if (ImageCodec::ParseParam(param)) {
98       return true;
99     }
100     if (param == "sjpeg" || param.find("cjpeg") != std::string::npos) {
101       jpeg_encoder_ = param;
102       return true;
103     }
104 #if JPEGXL_ENABLE_JPEGLI
105     if (param == "enc-jpegli") {
106       jpeg_encoder_ = "jpegli";
107       return true;
108     }
109 #endif
110     if (param.compare(0, 3, "yuv") == 0) {
111       chroma_subsampling_ = param.substr(3);
112       return true;
113     }
114     if (param.compare(0, 4, "psnr") == 0) {
115       psnr_target_ = std::stof(param.substr(4));
116       return true;
117     }
118     if (param[0] == 'p') {
119       progressive_id_ = strtol(param.substr(1).c_str(), nullptr, 10);
120       return true;
121     }
122     if (param == "fix") {
123       fix_codes_ = true;
124       return true;
125     }
126     if (param[0] == 'Q') {
127       libjpeg_quality_ = strtol(param.substr(1).c_str(), nullptr, 10);
128       return true;
129     }
130     if (param.compare(0, 3, "YUV") == 0) {
131       if (param.size() != 6) return false;
132       libjpeg_chroma_subsampling_ = param.substr(3);
133       return true;
134     }
135     if (param == "noaq") {
136       enable_adaptive_quant_ = false;
137       return true;
138     }
139 #if JPEGXL_ENABLE_JPEGLI
140     if (param == "xyb") {
141       xyb_mode_ = true;
142       return true;
143     }
144     if (param == "std") {
145       use_std_tables_ = true;
146       return true;
147     }
148     if (param == "dec-jpegli") {
149       jpeg_decoder_ = "jpegli";
150       return true;
151     }
152     if (param.substr(0, 2) == "bd") {
153       bitdepth_ = strtol(param.substr(2).c_str(), nullptr, 10);
154       return true;
155     }
156     if (param.substr(0, 6) == "cquant") {
157       num_colors_ = strtol(param.substr(6).c_str(), nullptr, 10);
158       return true;
159     }
160 #endif
161     return false;
162   }
163
164   bool IgnoreAlpha() const override { return true; }
165
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;
183       JXL_RETURN_IF_ERROR(
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");
199       }
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);
207 #else
208       return JXL_FAILURE("Not supported on this build");
209 #endif
210     }
211
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,
217         0};
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_;
225       if (!xyb_mode_) {
226         settings.use_std_quant_tables = use_std_tables_;
227       }
228       if (enc_quality_set_) {
229         settings.quality = q_target_;
230       } else {
231         settings.distance = butteraugli_target_;
232       }
233       if (progressive_id_ >= 0) {
234         settings.progressive_level = progressive_id_;
235       }
236       if (psnr_target_ > 0) {
237         settings.psnr_target = psnr_target_;
238       }
239       if (jpegargs->search_tolerance > 0) {
240         settings.search_tolerance = 0.01f * jpegargs->search_tolerance;
241       }
242       if (jpegargs->search_d_min > 0) {
243         settings.min_distance = jpegargs->search_d_min;
244       }
245       if (jpegargs->search_d_max > 0) {
246         settings.max_distance = jpegargs->search_d_max;
247       }
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();
254       JXL_RETURN_IF_ERROR(
255           jxl::extras::EncodeJpeg(ppf, settings, pool, compressed));
256       const double end = jxl::Now();
257       elapsed = end - start;
258 #endif
259     } else {
260       jxl::extras::EncodedImage encoded;
261       std::unique_ptr<jxl::extras::Encoder> encoder =
262           jxl::extras::GetJPEGEncoder();
263       if (!encoder) {
264         fprintf(stderr, "libjpeg codec is not supported\n");
265         return false;
266       }
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_);
273       }
274       if (progressive_id_ >= 0) {
275         encoder->SetOption("progressive", std::to_string(progressive_id_));
276       }
277       if (libjpeg_quality_ > 0) {
278         encoder->SetOption("libjpeg_quality", std::to_string(libjpeg_quality_));
279       }
280       if (!libjpeg_chroma_subsampling_.empty()) {
281         encoder->SetOption("libjpeg_chroma_subsampling",
282                            libjpeg_chroma_subsampling_);
283       }
284       if (fix_codes_) {
285         encoder->SetOption("optimize", "OFF");
286       }
287       if (!enable_adaptive_quant_) {
288         encoder->SetOption("adaptive_q", "OFF");
289       }
290       if (psnr_target_ > 0) {
291         encoder->SetOption("psnr", std::to_string(psnr_target_));
292       }
293       if (!jpegargs->base_quant_fn.empty()) {
294         encoder->SetOption("base_quant_fn", jpegargs->base_quant_fn);
295       }
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();
308     }
309     speed_stats->NotifyElapsed(elapsed);
310     return true;
311   }
312
313   Status Decompress(const std::string& filename,
314                     const Span<const uint8_t> compressed, ThreadPool* pool,
315                     CodecInOut* io,
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_;
327       JXL_RETURN_IF_ERROR(
328           jxl::extras::DecodeJpeg(jpeg_bytes, dparams, pool, &ppf));
329       const double end = jxl::Now();
330       speed_stats->NotifyElapsed(end - start);
331 #endif
332     } else {
333       const double start = jxl::Now();
334       jxl::extras::JPGDecompressParams dparams;
335       dparams.num_colors = num_colors_;
336       JXL_RETURN_IF_ERROR(
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);
341     }
342     JXL_RETURN_IF_ERROR(
343         jxl::extras::ConvertPackedPixelFileToCodecInOut(ppf, pool, io));
344     return true;
345   }
346
347  protected:
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;
360 #endif
361   bool enable_adaptive_quant_ = true;
362   // JPEG decoder and its parameters
363   std::string jpeg_decoder_ = "libjpeg";
364   int num_colors_ = 0;
365 #if JPEGXL_ENABLE_JPEGLI
366   size_t bitdepth_ = 8;
367 #endif
368 };
369
370 ImageCodec* CreateNewJPEGCodec(const BenchmarkArgs& args) {
371   return new JPEGCodec(args);
372 }
373
374 }  // namespace tools
375 }  // namespace jpegxl