benchmark_codec_jxl.cc (14546B)
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_jxl.h" 6 7 #include <jxl/color_encoding.h> 8 #include <jxl/encode.h> 9 #include <jxl/stats.h> 10 #include <jxl/types.h> 11 12 #include <cstdint> 13 #include <cstdlib> 14 #include <cstring> 15 #include <memory> 16 #include <sstream> 17 #include <string> 18 #include <utility> 19 #include <vector> 20 21 #include "lib/extras/dec/jxl.h" 22 #include "lib/extras/enc/apng.h" 23 #include "lib/extras/enc/encode.h" 24 #include "lib/extras/enc/jxl.h" 25 #include "lib/extras/packed_image.h" 26 #include "lib/extras/time.h" 27 #include "lib/jxl/base/status.h" 28 #include "lib/jxl/image.h" 29 #include "tools/benchmark/benchmark_args.h" 30 #include "tools/benchmark/benchmark_codec.h" 31 #include "tools/benchmark/benchmark_file_io.h" 32 #include "tools/benchmark/benchmark_stats.h" 33 #include "tools/file_io.h" 34 #include "tools/speed_stats.h" 35 #include "tools/thread_pool_internal.h" 36 37 namespace jpegxl { 38 namespace tools { 39 40 using ::jxl::Image3F; 41 using ::jxl::extras::EncodedImage; 42 using ::jxl::extras::JXLCompressParams; 43 using ::jxl::extras::JXLDecompressParams; 44 using ::jxl::extras::PackedFrame; 45 using ::jxl::extras::PackedPixelFile; 46 47 struct JxlArgs { 48 bool qprogressive; // progressive with shift-quantization. 49 bool progressive; 50 int progressive_dc; 51 52 Override noise; 53 Override dots; 54 Override patches; 55 56 std::string debug_image_dir; 57 }; 58 59 static JxlArgs* const jxlargs = new JxlArgs; 60 61 Status AddCommandLineOptionsJxlCodec(BenchmarkArgs* args) { 62 args->AddFlag(&jxlargs->qprogressive, "qprogressive", 63 "Enable quantized progressive mode for AC.", false); 64 args->AddFlag(&jxlargs->progressive, "progressive", 65 "Enable progressive mode for AC.", false); 66 args->AddSigned(&jxlargs->progressive_dc, "progressive_dc", 67 "Enable progressive mode for DC.", -1); 68 69 args->AddOverride(&jxlargs->noise, "noise", 70 "Enable(1)/disable(0) noise generation."); 71 args->AddOverride(&jxlargs->dots, "dots", 72 "Enable(1)/disable(0) dots generation."); 73 args->AddOverride(&jxlargs->patches, "patches", 74 "Enable(1)/disable(0) patch dictionary."); 75 76 args->AddString( 77 &jxlargs->debug_image_dir, "debug_image_dir", 78 "If not empty, saves debug images for each " 79 "input image and each codec that provides it to this directory."); 80 81 return true; 82 } 83 84 Status ValidateArgsJxlCodec(BenchmarkArgs* args) { return true; } 85 86 inline bool ParseEffort(const std::string& s, int* out) { 87 if (s == "lightning") { 88 *out = 1; 89 return true; 90 } else if (s == "thunder") { 91 *out = 2; 92 return true; 93 } else if (s == "falcon") { 94 *out = 3; 95 return true; 96 } else if (s == "cheetah") { 97 *out = 4; 98 return true; 99 } else if (s == "hare") { 100 *out = 5; 101 return true; 102 } else if (s == "fast" || s == "wombat") { 103 *out = 6; 104 return true; 105 } else if (s == "squirrel") { 106 *out = 7; 107 return true; 108 } else if (s == "kitten") { 109 *out = 8; 110 return true; 111 } else if (s == "guetzli" || s == "tortoise") { 112 *out = 9; 113 return true; 114 } else if (s == "glacier") { 115 *out = 10; 116 return true; 117 } 118 size_t st = static_cast<size_t>(strtoull(s.c_str(), nullptr, 0)); 119 if (st <= 10 && st >= 1) { 120 *out = st; 121 return true; 122 } 123 return false; 124 } 125 126 class JxlCodec : public ImageCodec { 127 public: 128 explicit JxlCodec(const BenchmarkArgs& args) 129 : ImageCodec(args), stats_(nullptr, JxlEncoderStatsDestroy) {} 130 131 Status ParseParam(const std::string& param) override { 132 const std::string kMaxPassesPrefix = "max_passes="; 133 const std::string kDownsamplingPrefix = "downsampling="; 134 const std::string kResamplingPrefix = "resampling="; 135 const std::string kEcResamplingPrefix = "ec_resampling="; 136 int val; 137 float fval; 138 if (param.substr(0, kResamplingPrefix.size()) == kResamplingPrefix) { 139 std::istringstream parser(param.substr(kResamplingPrefix.size())); 140 int resampling; 141 parser >> resampling; 142 cparams_.AddOption(JXL_ENC_FRAME_SETTING_RESAMPLING, resampling); 143 } else if (param.substr(0, kEcResamplingPrefix.size()) == 144 kEcResamplingPrefix) { 145 std::istringstream parser(param.substr(kEcResamplingPrefix.size())); 146 int ec_resampling; 147 parser >> ec_resampling; 148 cparams_.AddOption(JXL_ENC_FRAME_SETTING_EXTRA_CHANNEL_RESAMPLING, 149 ec_resampling); 150 } else if (ImageCodec::ParseParam(param)) { 151 // Nothing to do. 152 } else if (param == "uint8") { 153 uint8_ = true; 154 } else if (param[0] == 'D') { 155 cparams_.alpha_distance = strtof(param.substr(1).c_str(), nullptr); 156 } else if (param.substr(0, kMaxPassesPrefix.size()) == kMaxPassesPrefix) { 157 std::istringstream parser(param.substr(kMaxPassesPrefix.size())); 158 parser >> dparams_.max_passes; 159 } else if (param.substr(0, kDownsamplingPrefix.size()) == 160 kDownsamplingPrefix) { 161 std::istringstream parser(param.substr(kDownsamplingPrefix.size())); 162 parser >> dparams_.max_downsampling; 163 } else if (ParseEffort(param, &val)) { 164 cparams_.AddOption(JXL_ENC_FRAME_SETTING_EFFORT, val); 165 } else if (param[0] == 'X') { 166 fval = strtof(param.substr(1).c_str(), nullptr); 167 cparams_.AddFloatOption( 168 JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT, fval); 169 } else if (param[0] == 'Y') { 170 fval = strtof(param.substr(1).c_str(), nullptr); 171 cparams_.AddFloatOption( 172 JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT, fval); 173 } else if (param[0] == 'p') { 174 val = strtol(param.substr(1).c_str(), nullptr, 10); 175 cparams_.AddOption(JXL_ENC_FRAME_SETTING_PALETTE_COLORS, val); 176 } else if (param == "lp") { 177 cparams_.AddOption(JXL_ENC_FRAME_SETTING_LOSSY_PALETTE, 1); 178 } else if (param[0] == 'C') { 179 val = strtol(param.substr(1).c_str(), nullptr, 10); 180 cparams_.AddOption(JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE, val); 181 } else if (param[0] == 'c') { 182 val = strtol(param.substr(1).c_str(), nullptr, 10); 183 cparams_.AddOption(JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM, val); 184 has_ctransform_ = true; 185 } else if (param[0] == 'I') { 186 fval = strtof(param.substr(1).c_str(), nullptr); 187 cparams_.AddFloatOption( 188 JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT, fval * 100.0); 189 } else if (param[0] == 'E') { 190 val = strtol(param.substr(1).c_str(), nullptr, 10); 191 cparams_.AddOption(JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS, val); 192 } else if (param[0] == 'P') { 193 val = strtol(param.substr(1).c_str(), nullptr, 10); 194 cparams_.AddOption(JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR, val); 195 } else if (param == "slow") { 196 cparams_.AddFloatOption( 197 JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT, 50.0); 198 } else if (param == "R") { 199 cparams_.AddOption(JXL_ENC_FRAME_SETTING_RESPONSIVE, 1); 200 } else if (param[0] == 'R') { 201 val = strtol(param.substr(1).c_str(), nullptr, 10); 202 cparams_.AddOption(JXL_ENC_FRAME_SETTING_RESPONSIVE, val); 203 } else if (param == "m") { 204 cparams_.AddOption(JXL_ENC_FRAME_SETTING_MODULAR, 1); 205 cparams_.AddOption(JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM, 1); // kNone 206 modular_mode_ = true; 207 } else if (param.substr(0, 3) == "gab") { 208 val = strtol(param.substr(3).c_str(), nullptr, 10); 209 if (val != 0 && val != 1) { 210 return JXL_FAILURE("Invalid gab value"); 211 } 212 cparams_.AddOption(JXL_ENC_FRAME_SETTING_GABORISH, val); 213 } else if (param[0] == 'g') { 214 val = strtol(param.substr(1).c_str(), nullptr, 10); 215 if (val < 0 || val > 3) { 216 return JXL_FAILURE("Invalid group size shift value"); 217 } 218 cparams_.AddOption(JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE, val); 219 } else if (param == "plt") { 220 cparams_.AddOption(JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS, 0); 221 cparams_.AddFloatOption( 222 JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT, 0.0f); 223 cparams_.AddOption(JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR, 0); 224 cparams_.AddOption(JXL_ENC_FRAME_SETTING_RESPONSIVE, 0); 225 cparams_.AddOption(JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE, 0); 226 cparams_.AddOption(JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT, 227 0); 228 cparams_.AddOption(JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT, 0); 229 } else if (param.substr(0, 3) == "epf") { 230 val = strtol(param.substr(3).c_str(), nullptr, 10); 231 if (val > 3) { 232 return JXL_FAILURE("Invalid epf value"); 233 } 234 cparams_.AddOption(JXL_ENC_FRAME_SETTING_EPF, val); 235 } else if (param.substr(0, 2) == "fi") { 236 val = strtol(param.substr(2).c_str(), nullptr, 10); 237 if (val != 0 && val != 1) { 238 return JXL_FAILURE("Invalid option value"); 239 } 240 cparams_.AddOption(JXL_ENC_FRAME_SETTING_USE_FULL_IMAGE_HEURISTICS, val); 241 } else if (param.substr(0, 3) == "buf") { 242 val = strtol(param.substr(3).c_str(), nullptr, 10); 243 if (val > 3) { 244 return JXL_FAILURE("Invalid buffering value"); 245 } 246 cparams_.AddOption(JXL_ENC_FRAME_SETTING_BUFFERING, val); 247 } else if (param.substr(0, 16) == "faster_decoding=") { 248 val = strtol(param.substr(16).c_str(), nullptr, 10); 249 cparams_.AddOption(JXL_ENC_FRAME_SETTING_DECODING_SPEED, val); 250 } else { 251 return JXL_FAILURE("Unrecognized param"); 252 } 253 return true; 254 } 255 256 Status Compress(const std::string& filename, const PackedPixelFile& ppf, 257 ThreadPool* pool, std::vector<uint8_t>* compressed, 258 jpegxl::tools::SpeedStats* speed_stats) override { 259 cparams_.runner = pool->runner(); 260 cparams_.runner_opaque = pool->runner_opaque(); 261 cparams_.distance = butteraugli_target_; 262 cparams_.AddOption(JXL_ENC_FRAME_SETTING_NOISE, 263 static_cast<int>(jxlargs->noise)); 264 cparams_.AddOption(JXL_ENC_FRAME_SETTING_DOTS, 265 static_cast<int>(jxlargs->dots)); 266 cparams_.AddOption(JXL_ENC_FRAME_SETTING_PATCHES, 267 static_cast<int>(jxlargs->patches)); 268 cparams_.AddOption(JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC, 269 TO_JXL_BOOL(jxlargs->progressive)); 270 cparams_.AddOption(JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC, 271 TO_JXL_BOOL(jxlargs->qprogressive)); 272 cparams_.AddOption(JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC, 273 jxlargs->progressive_dc); 274 if (butteraugli_target_ > 0.f && modular_mode_ && !has_ctransform_) { 275 // Reset color transform to default XYB for lossy modular. 276 cparams_.AddOption(JXL_ENC_FRAME_SETTING_COLOR_TRANSFORM, -1); 277 } 278 std::string debug_prefix; 279 SetDebugImageCallback(filename, &debug_prefix, &cparams_); 280 if (args_.print_more_stats) { 281 stats_.reset(JxlEncoderStatsCreate()); 282 cparams_.stats = stats_.get(); 283 } 284 const double start = jxl::Now(); 285 JXL_RETURN_IF_ERROR(jxl::extras::EncodeImageJXL( 286 cparams_, ppf, /*jpeg_bytes=*/nullptr, compressed)); 287 const double end = jxl::Now(); 288 speed_stats->NotifyElapsed(end - start); 289 return true; 290 } 291 292 Status Decompress(const std::string& filename, 293 const Span<const uint8_t> compressed, ThreadPool* pool, 294 PackedPixelFile* ppf, 295 jpegxl::tools::SpeedStats* speed_stats) override { 296 dparams_.runner = pool->runner(); 297 dparams_.runner_opaque = pool->runner_opaque(); 298 JxlDataType data_type = uint8_ ? JXL_TYPE_UINT8 : JXL_TYPE_UINT16; 299 dparams_.accepted_formats = {{3, data_type, JXL_LITTLE_ENDIAN, 0}, 300 {4, data_type, JXL_LITTLE_ENDIAN, 0}}; 301 // By default, the decoder will undo exif orientation, giving an image 302 // with identity exif rotation as result. However, the benchmark does 303 // not undo exif orientation of the originals, and compares against the 304 // originals, so we must set the option to keep the original orientation 305 // instead. 306 dparams_.keep_orientation = true; 307 size_t decoded_bytes; 308 const double start = jxl::Now(); 309 JXL_RETURN_IF_ERROR(jxl::extras::DecodeImageJXL( 310 compressed.data(), compressed.size(), dparams_, &decoded_bytes, ppf)); 311 const double end = jxl::Now(); 312 speed_stats->NotifyElapsed(end - start); 313 return true; 314 } 315 316 void GetMoreStats(BenchmarkStats* stats) override { 317 stats->jxl_stats.num_inputs += 1; 318 JxlEncoderStatsMerge(stats->jxl_stats.stats.get(), stats_.get()); 319 } 320 321 protected: 322 JXLCompressParams cparams_; 323 bool has_ctransform_ = false; 324 bool modular_mode_ = false; 325 JXLDecompressParams dparams_; 326 bool uint8_ = false; 327 std::unique_ptr<JxlEncoderStats, decltype(JxlEncoderStatsDestroy)*> stats_; 328 329 private: 330 void SetDebugImageCallback(const std::string& filename, 331 std::string* debug_prefix, 332 JXLCompressParams* cparams) { 333 if (jxlargs->debug_image_dir.empty()) return; 334 *debug_prefix = JoinPath(jxlargs->debug_image_dir, FileBaseName(filename)) + 335 ".jxl:" + params_ + ".dbg/"; 336 JXL_CHECK(MakeDir(*debug_prefix)); 337 cparams->debug_image_opaque = debug_prefix; 338 cparams->debug_image = [](void* opaque, const char* label, size_t xsize, 339 size_t ysize, const JxlColorEncoding* color, 340 const uint16_t* pixels) { 341 auto encoder = jxl::extras::GetAPNGEncoder(); 342 JXL_CHECK(encoder); 343 PackedPixelFile debug_ppf; 344 JxlPixelFormat format{3, JXL_TYPE_UINT16, JXL_BIG_ENDIAN, 0}; 345 PackedFrame frame(xsize, ysize, format); 346 memcpy(frame.color.pixels(), pixels, 6 * xsize * ysize); 347 debug_ppf.frames.emplace_back(std::move(frame)); 348 debug_ppf.info.xsize = xsize; 349 debug_ppf.info.ysize = ysize; 350 debug_ppf.info.num_color_channels = 3; 351 debug_ppf.info.bits_per_sample = 16; 352 debug_ppf.color_encoding = *color; 353 EncodedImage encoded; 354 JXL_CHECK(encoder->Encode(debug_ppf, &encoded, nullptr)); 355 JXL_CHECK(!encoded.bitstreams.empty()); 356 std::string* debug_prefix = reinterpret_cast<std::string*>(opaque); 357 std::string fn = *debug_prefix + std::string(label) + ".png"; 358 WriteFile(fn, encoded.bitstreams[0]); 359 }; 360 } 361 }; 362 363 ImageCodec* CreateNewJxlCodec(const BenchmarkArgs& args) { 364 return new JxlCodec(args); 365 } 366 367 } // namespace tools 368 } // namespace jpegxl