benchmark_codec_jpeg.cc (12751B)
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 <jxl/types.h> 11 #include <stdint.h> 12 #include <string.h> 13 14 #include <cmath> 15 #include <cstdlib> 16 #include <memory> 17 #include <sstream> 18 #include <string> 19 #include <vector> 20 21 #include "lib/extras/codec.h" 22 #include "lib/extras/enc/encode.h" 23 #include "lib/jxl/base/status.h" 24 #include "tools/benchmark/benchmark_args.h" 25 #include "tools/benchmark/benchmark_codec.h" 26 #include "tools/speed_stats.h" 27 28 #if JPEGXL_ENABLE_JPEGLI 29 #include "lib/extras/dec/jpegli.h" 30 #endif 31 #include "lib/extras/dec/jpg.h" 32 #if JPEGXL_ENABLE_JPEGLI 33 #include "lib/extras/enc/jpegli.h" 34 #endif 35 #include "lib/extras/enc/jpg.h" 36 #include "lib/extras/packed_image.h" 37 #include "lib/extras/time.h" 38 #include "lib/jxl/base/span.h" 39 #include "tools/benchmark/benchmark_utils.h" 40 #include "tools/file_io.h" 41 #include "tools/thread_pool_internal.h" 42 43 namespace jpegxl { 44 namespace tools { 45 46 struct JPEGArgs { 47 std::string base_quant_fn; 48 float search_q_start; 49 float search_q_min; 50 float search_q_max; 51 float search_d_min; 52 float search_d_max; 53 int search_max_iters; 54 float search_tolerance; 55 float search_q_precision; 56 float search_first_iter_slope; 57 }; 58 59 static JPEGArgs* const jpegargs = new JPEGArgs; 60 61 #define SET_ENCODER_ARG(name) \ 62 if (jpegargs->name > 0) { \ 63 encoder->SetOption(#name, std::to_string(jpegargs->name)); \ 64 } 65 66 Status AddCommandLineOptionsJPEGCodec(BenchmarkArgs* args) { 67 args->AddString(&jpegargs->base_quant_fn, "qtables", 68 "Custom base quantization tables."); 69 args->AddFloat(&jpegargs->search_q_start, "search_q_start", 70 "Starting quality for quality-to-target search", 0.0f); 71 args->AddFloat(&jpegargs->search_q_min, "search_q_min", 72 "Minimum quality for quality-to-target search", 0.0f); 73 args->AddFloat(&jpegargs->search_q_max, "search_q_max", 74 "Maximum quality for quality-to-target search", 0.0f); 75 args->AddFloat(&jpegargs->search_d_min, "search_d_min", 76 "Minimum distance for quality-to-target search", 0.0f); 77 args->AddFloat(&jpegargs->search_d_max, "search_d_max", 78 "Maximum distance for quality-to-target search", 0.0f); 79 args->AddFloat(&jpegargs->search_tolerance, "search_tolerance", 80 "Percentage value, if quality-to-target search result " 81 "relative error is within this, search stops.", 82 0.0f); 83 args->AddFloat(&jpegargs->search_q_precision, "search_q_precision", 84 "If last quality change in quality-to-target search is " 85 "within this value, search stops.", 86 0.0f); 87 args->AddFloat(&jpegargs->search_first_iter_slope, "search_first_iter_slope", 88 "Slope of first extrapolation step in quality-to-target " 89 "search.", 90 0.0f); 91 args->AddSigned(&jpegargs->search_max_iters, "search_max_iters", 92 "Maximum search steps in quality-to-target search.", 0); 93 return true; 94 } 95 96 class JPEGCodec : public ImageCodec { 97 public: 98 explicit JPEGCodec(const BenchmarkArgs& args) : ImageCodec(args) {} 99 100 Status ParseParam(const std::string& param) override { 101 if (param[0] == 'q' && ImageCodec::ParseParam(param)) { 102 enc_quality_set_ = true; 103 return true; 104 } 105 if (ImageCodec::ParseParam(param)) { 106 return true; 107 } 108 if (param == "sjpeg" || param.find("cjpeg") != std::string::npos) { 109 jpeg_encoder_ = param; 110 return true; 111 } 112 #if JPEGXL_ENABLE_JPEGLI 113 if (param == "enc-jpegli") { 114 jpeg_encoder_ = "jpegli"; 115 return true; 116 } 117 #endif 118 if (param.compare(0, 3, "yuv") == 0) { 119 chroma_subsampling_ = param.substr(3); 120 return true; 121 } 122 if (param.compare(0, 4, "psnr") == 0) { 123 psnr_target_ = std::stof(param.substr(4)); 124 return true; 125 } 126 if (param[0] == 'p') { 127 progressive_id_ = strtol(param.substr(1).c_str(), nullptr, 10); 128 return true; 129 } 130 if (param == "fix") { 131 fix_codes_ = true; 132 return true; 133 } 134 if (param[0] == 'Q') { 135 libjpeg_quality_ = strtol(param.substr(1).c_str(), nullptr, 10); 136 return true; 137 } 138 if (param.compare(0, 3, "YUV") == 0) { 139 if (param.size() != 6) return false; 140 libjpeg_chroma_subsampling_ = param.substr(3); 141 return true; 142 } 143 if (param == "noaq") { 144 enable_adaptive_quant_ = false; 145 return true; 146 } 147 #if JPEGXL_ENABLE_JPEGLI 148 if (param == "xyb") { 149 xyb_mode_ = true; 150 return true; 151 } 152 if (param == "std") { 153 use_std_tables_ = true; 154 return true; 155 } 156 if (param == "dec-jpegli") { 157 jpeg_decoder_ = "jpegli"; 158 return true; 159 } 160 if (param.substr(0, 2) == "bd") { 161 bitdepth_ = strtol(param.substr(2).c_str(), nullptr, 10); 162 return true; 163 } 164 if (param.substr(0, 6) == "cquant") { 165 num_colors_ = strtol(param.substr(6).c_str(), nullptr, 10); 166 return true; 167 } 168 #endif 169 return false; 170 } 171 172 bool IgnoreAlpha() const override { return true; } 173 174 Status Compress(const std::string& filename, const PackedPixelFile& ppf, 175 ThreadPool* pool, std::vector<uint8_t>* compressed, 176 jpegxl::tools::SpeedStats* speed_stats) override { 177 if (jpeg_encoder_.find("cjpeg") != std::string::npos) { 178 // Not supported on Windows due to Linux-specific functions. 179 // Not supported in Android NDK before API 28. 180 #if !defined(_WIN32) && !defined(__EMSCRIPTEN__) && \ 181 (!defined(__ANDROID_API__) || __ANDROID_API__ >= 28) 182 const std::string basename = GetBaseName(filename); 183 TemporaryFile in_file(basename, "pnm"); 184 TemporaryFile encoded_file(basename, "jpg"); 185 std::string in_filename; 186 std::string encoded_filename; 187 JXL_RETURN_IF_ERROR(in_file.GetFileName(&in_filename)); 188 JXL_RETURN_IF_ERROR(encoded_file.GetFileName(&encoded_filename)); 189 std::vector<uint8_t> encoded; 190 JXL_RETURN_IF_ERROR(Encode(ppf, in_filename, &encoded, pool)); 191 JXL_RETURN_IF_ERROR(WriteFile(in_filename, encoded)); 192 std::string compress_command = jpeg_encoder_; 193 std::vector<std::string> arguments; 194 arguments.emplace_back("-outfile"); 195 arguments.push_back(encoded_filename); 196 arguments.emplace_back("-quality"); 197 arguments.push_back(std::to_string(static_cast<int>(q_target_))); 198 arguments.emplace_back("-sample"); 199 if (chroma_subsampling_ == "444") { 200 arguments.emplace_back("1x1"); 201 } else if (chroma_subsampling_ == "420") { 202 arguments.emplace_back("2x2"); 203 } else if (!chroma_subsampling_.empty()) { 204 return JXL_FAILURE("Unsupported chroma subsampling"); 205 } 206 arguments.emplace_back("-optimize"); 207 arguments.push_back(in_filename); 208 const double start = jxl::Now(); 209 JXL_RETURN_IF_ERROR(RunCommand(compress_command, arguments, false)); 210 const double end = jxl::Now(); 211 speed_stats->NotifyElapsed(end - start); 212 return ReadFile(encoded_filename, compressed); 213 #else 214 return JXL_FAILURE("Not supported on this build"); 215 #endif 216 } 217 218 double elapsed = 0.0; 219 if (jpeg_encoder_ == "jpegli") { 220 #if JPEGXL_ENABLE_JPEGLI 221 jxl::extras::JpegSettings settings; 222 settings.xyb = xyb_mode_; 223 if (!xyb_mode_) { 224 settings.use_std_quant_tables = use_std_tables_; 225 } 226 if (enc_quality_set_) { 227 settings.quality = q_target_; 228 } else { 229 settings.distance = butteraugli_target_; 230 } 231 if (progressive_id_ >= 0) { 232 settings.progressive_level = progressive_id_; 233 } 234 if (psnr_target_ > 0) { 235 settings.psnr_target = psnr_target_; 236 } 237 if (jpegargs->search_tolerance > 0) { 238 settings.search_tolerance = 0.01f * jpegargs->search_tolerance; 239 } 240 if (jpegargs->search_d_min > 0) { 241 settings.min_distance = jpegargs->search_d_min; 242 } 243 if (jpegargs->search_d_max > 0) { 244 settings.max_distance = jpegargs->search_d_max; 245 } 246 settings.chroma_subsampling = chroma_subsampling_; 247 settings.use_adaptive_quantization = enable_adaptive_quant_; 248 settings.libjpeg_quality = libjpeg_quality_; 249 settings.libjpeg_chroma_subsampling = libjpeg_chroma_subsampling_; 250 settings.optimize_coding = !fix_codes_; 251 const double start = jxl::Now(); 252 JXL_RETURN_IF_ERROR( 253 jxl::extras::EncodeJpeg(ppf, settings, pool, compressed)); 254 const double end = jxl::Now(); 255 elapsed = end - start; 256 #endif 257 } else { 258 jxl::extras::EncodedImage encoded; 259 std::unique_ptr<jxl::extras::Encoder> encoder = 260 jxl::extras::GetJPEGEncoder(); 261 if (!encoder) { 262 fprintf(stderr, "libjpeg codec is not supported\n"); 263 return false; 264 } 265 std::ostringstream os; 266 os << static_cast<int>(std::round(q_target_)); 267 encoder->SetOption("q", os.str()); 268 encoder->SetOption("jpeg_encoder", jpeg_encoder_); 269 if (!chroma_subsampling_.empty()) { 270 encoder->SetOption("chroma_subsampling", chroma_subsampling_); 271 } 272 if (progressive_id_ >= 0) { 273 encoder->SetOption("progressive", std::to_string(progressive_id_)); 274 } 275 if (libjpeg_quality_ > 0) { 276 encoder->SetOption("libjpeg_quality", std::to_string(libjpeg_quality_)); 277 } 278 if (!libjpeg_chroma_subsampling_.empty()) { 279 encoder->SetOption("libjpeg_chroma_subsampling", 280 libjpeg_chroma_subsampling_); 281 } 282 if (fix_codes_) { 283 encoder->SetOption("optimize", "OFF"); 284 } 285 if (!enable_adaptive_quant_) { 286 encoder->SetOption("adaptive_q", "OFF"); 287 } 288 if (psnr_target_ > 0) { 289 encoder->SetOption("psnr", std::to_string(psnr_target_)); 290 } 291 if (!jpegargs->base_quant_fn.empty()) { 292 encoder->SetOption("base_quant_fn", jpegargs->base_quant_fn); 293 } 294 SET_ENCODER_ARG(search_q_start); 295 SET_ENCODER_ARG(search_q_min); 296 SET_ENCODER_ARG(search_q_max); 297 SET_ENCODER_ARG(search_q_precision); 298 SET_ENCODER_ARG(search_tolerance); 299 SET_ENCODER_ARG(search_first_iter_slope); 300 SET_ENCODER_ARG(search_max_iters); 301 const double start = jxl::Now(); 302 JXL_RETURN_IF_ERROR(encoder->Encode(ppf, &encoded, pool)); 303 const double end = jxl::Now(); 304 elapsed = end - start; 305 *compressed = encoded.bitstreams.back(); 306 } 307 speed_stats->NotifyElapsed(elapsed); 308 return true; 309 } 310 311 Status Decompress(const std::string& filename, 312 const Span<const uint8_t> compressed, ThreadPool* pool, 313 jxl::extras::PackedPixelFile* ppf, 314 jpegxl::tools::SpeedStats* speed_stats) override { 315 if (jpeg_decoder_ == "jpegli") { 316 #if JPEGXL_ENABLE_JPEGLI 317 std::vector<uint8_t> jpeg_bytes(compressed.data(), 318 compressed.data() + compressed.size()); 319 const double start = jxl::Now(); 320 jxl::extras::JpegDecompressParams dparams; 321 dparams.output_data_type = 322 bitdepth_ > 8 ? JXL_TYPE_UINT16 : JXL_TYPE_UINT8; 323 dparams.num_colors = num_colors_; 324 JXL_RETURN_IF_ERROR( 325 jxl::extras::DecodeJpeg(jpeg_bytes, dparams, pool, ppf)); 326 const double end = jxl::Now(); 327 speed_stats->NotifyElapsed(end - start); 328 #endif 329 } else { 330 const double start = jxl::Now(); 331 jxl::extras::JPGDecompressParams dparams; 332 dparams.num_colors = num_colors_; 333 JXL_RETURN_IF_ERROR( 334 jxl::extras::DecodeImageJPG(compressed, jxl::extras::ColorHints(), 335 ppf, /*constraints=*/nullptr, &dparams)); 336 const double end = jxl::Now(); 337 speed_stats->NotifyElapsed(end - start); 338 } 339 return true; 340 } 341 342 protected: 343 // JPEG encoder and its parameters 344 std::string jpeg_encoder_ = "libjpeg"; 345 std::string chroma_subsampling_; 346 int progressive_id_ = -1; 347 bool fix_codes_ = false; 348 float psnr_target_ = 0.0f; 349 bool enc_quality_set_ = false; 350 int libjpeg_quality_ = 0; 351 std::string libjpeg_chroma_subsampling_; 352 #if JPEGXL_ENABLE_JPEGLI 353 bool xyb_mode_ = false; 354 bool use_std_tables_ = false; 355 #endif 356 bool enable_adaptive_quant_ = true; 357 // JPEG decoder and its parameters 358 std::string jpeg_decoder_ = "libjpeg"; 359 int num_colors_ = 0; 360 #if JPEGXL_ENABLE_JPEGLI 361 size_t bitdepth_ = 8; 362 #endif 363 }; 364 365 ImageCodec* CreateNewJPEGCodec(const BenchmarkArgs& args) { 366 return new JPEGCodec(args); 367 } 368 369 } // namespace tools 370 } // namespace jpegxl