djxl_main.cc (24152B)
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 6 #include <jxl/decode.h> 7 #include <jxl/thread_parallel_runner.h> 8 #include <jxl/thread_parallel_runner_cxx.h> 9 #include <jxl/types.h> 10 11 #include <cmath> 12 #include <cstddef> 13 #include <cstdint> 14 #include <cstdio> 15 #include <cstdlib> 16 #include <cstring> 17 #include <iostream> 18 #include <memory> 19 #include <sstream> 20 #include <string> 21 #include <vector> 22 23 #include "lib/extras/alpha_blend.h" 24 #include "lib/extras/dec/decode.h" 25 #include "lib/extras/dec/jxl.h" 26 #include "lib/extras/enc/apng.h" 27 #include "lib/extras/enc/encode.h" 28 #include "lib/extras/enc/exr.h" 29 #include "lib/extras/enc/jpg.h" 30 #include "lib/extras/packed_image.h" 31 #include "lib/extras/time.h" 32 #include "lib/jxl/base/printf_macros.h" 33 #include "tools/cmdline.h" 34 #include "tools/codec_config.h" 35 #include "tools/file_io.h" 36 #include "tools/speed_stats.h" 37 38 namespace jpegxl { 39 namespace tools { 40 41 struct DecompressArgs { 42 DecompressArgs() = default; 43 44 void AddCommandLineOptions(CommandLineParser* cmdline) { 45 std::string output_help("The output format can be "); 46 if (jxl::extras::GetAPNGEncoder()) { 47 output_help.append("PNG, APNG, "); 48 } 49 if (jxl::extras::GetJPEGEncoder()) { 50 output_help.append("JPEG, "); 51 } else { 52 output_help.append("JPEG (lossless reconstruction only), "); 53 } 54 if (jxl::extras::GetEXREncoder()) { 55 output_help.append("EXR, "); 56 } 57 output_help.append( 58 "PGM (for greyscale input), PPM (for color input), PNM, PFM, or PAM.\n" 59 " To extract metadata, use output format EXIF, XMP, or JUMBF.\n" 60 " The format is selected based on extension ('filename.png') or can " 61 "be overwritten by using --output_format.\n" 62 " Use '-' for output to stdout (e.g. '- --output_format ppm')"); 63 cmdline->AddPositionalOption( 64 "INPUT", /* required = */ true, 65 "The compressed input file (JXL). Use '-' for input from stdin.", 66 &file_in); 67 68 cmdline->AddPositionalOption("OUTPUT", /* required = */ true, output_help, 69 &file_out); 70 71 cmdline->AddHelpText("\nBasic options:", 0); 72 73 cmdline->AddOptionValue( 74 '\0', "output_format", "OUTPUT_FORMAT_DESC", 75 "Set the output format. This overrides the output format detected from " 76 "a potential file extension in the OUTPUT filename.\n" 77 "Must be one of png, apng, jpg, jpeg, npy, pgx, pam, pgm, ppm, pnm, " 78 "pfm, exr, exif, xmp, xml, jumb, jumbf when converted to lower case.", 79 &output_format, &ParseString, 1); 80 81 cmdline->AddOptionFlag('V', "version", "Print version number and exit.", 82 &version, &SetBooleanTrue, 0); 83 cmdline->AddOptionFlag('\0', "quiet", "Silence output (except for errors).", 84 &quiet, &SetBooleanTrue, 0); 85 cmdline->AddOptionFlag('v', "verbose", 86 "Verbose output; can be repeated and also applies " 87 "to help (!).", 88 &verbose, &SetBooleanTrue); 89 90 cmdline->AddHelpText("\nAdvanced options:", 1); 91 92 cmdline->AddOptionValue('\0', "num_threads", "N", 93 "Number of worker threads (-1 == use machine " 94 "default, 0 == do not use multithreading).", 95 &num_threads, &ParseSigned, 1); 96 97 opt_bits_per_sample_id = cmdline->AddOptionValue( 98 '\0', "bits_per_sample", "N", 99 "Sets the output bit depth. The value 0 (default for PNM) " 100 "means the original (input) bit depth.\n" 101 " The value -1 (default for other codecs) means it depends on the " 102 "output format capabilities\n" 103 " and the input bit depth (e.g. decoding a 12-bit image to PNG will " 104 "produce a 16-bit PNG).", 105 &bits_per_sample, &ParseSigned, 1); 106 107 cmdline->AddOptionValue('\0', "display_nits", "N", 108 "If set to a non-zero value, tone maps the image " 109 "the given peak display luminance.", 110 &display_nits, &ParseDouble, 1); 111 112 cmdline->AddOptionValue( 113 '\0', "color_space", "COLORSPACE_DESC", 114 "Sets the desired output color space of the image. For example:\n" 115 " --color_space=RGB_D65_SRG_Per_SRG is sRGB with perceptual " 116 "rendering intent\n" 117 " --color_space=RGB_D65_202_Rel_PeQ is Rec.2100 PQ with relative " 118 "rendering intent", 119 &color_space, &ParseString, 1); 120 121 cmdline->AddOptionValue('s', "downsampling", "1|2|4|8", 122 "If the input JXL stream is contains hints for " 123 "target downsampling ratios,\n" 124 " only decode what is needed to produce an " 125 "image intended for this downsampling ratio.", 126 &downsampling, &ParseUint32, 1); 127 128 cmdline->AddOptionFlag('\0', "allow_partial_files", 129 "Allow decoding of truncated files.", 130 &allow_partial_files, &SetBooleanTrue, 1); 131 132 if (jxl::extras::GetJPEGEncoder()) { 133 cmdline->AddOptionFlag( 134 'j', "pixels_to_jpeg", 135 "By default, if the input JXL is a recompressed JPEG file, " 136 "djxl reconstructs that JPEG file.\n" 137 " This flag causes the decoder to instead decode to pixels and " 138 "encode a new (lossy) JPEG.", 139 &pixels_to_jpeg, &SetBooleanTrue, 1); 140 141 opt_jpeg_quality_id = cmdline->AddOptionValue( 142 'q', "jpeg_quality", "N", 143 "Sets the JPEG output quality, default is 95. " 144 "Setting this option implies --pixels_to_jpeg.", 145 &jpeg_quality, &ParseUnsigned, 1); 146 } 147 148 cmdline->AddHelpText("\nOptions for experimentation / benchmarking:", 2); 149 150 cmdline->AddOptionValue('\0', "num_reps", "N", 151 "Sets the number of times to decompress the image. " 152 "Useful for benchmarking. Default is 1.", 153 &num_reps, &ParseUnsigned, 2); 154 155 cmdline->AddOptionFlag('\0', "disable_output", 156 "No output file will be written (for benchmarking)", 157 &disable_output, &SetBooleanTrue, 2); 158 159 cmdline->AddOptionFlag('\0', "output_extra_channels", 160 "If set, all extra channels will be written either " 161 "as part of the main output file (e.g. alpha " 162 "channel in png) or as separate output files with " 163 "suffix -ecN in their names. If not set, the " 164 "(first) alpha channel will only be written when " 165 "the output format supports alpha channels and all " 166 "other extra channels won't be decoded. Files are " 167 "concatenated when outputting to stdout.", 168 &output_extra_channels, &SetBooleanTrue, 2); 169 170 cmdline->AddOptionFlag( 171 '\0', "output_frames", 172 "If set, all frames will be written either as part of the main output " 173 "file if that supports animation, or as separate output files with " 174 "suffix -N in their names. Files are concatenated when outputting to " 175 "stdout.", 176 &output_frames, &SetBooleanTrue, 2); 177 178 cmdline->AddOptionFlag('\0', "use_sjpeg", 179 "Use sjpeg instead of libjpeg for JPEG output.", 180 &use_sjpeg, &SetBooleanTrue, 2); 181 182 cmdline->AddOptionFlag('\0', "norender_spotcolors", 183 "Disables rendering of spot colors.", 184 &render_spotcolors, &SetBooleanFalse, 2); 185 186 cmdline->AddOptionValue('\0', "preview_out", "FILENAME", 187 "If specified, writes the preview image to this " 188 "file.", 189 &preview_out, &ParseString, 2); 190 191 cmdline->AddOptionValue( 192 '\0', "icc_out", "FILENAME", 193 "If specified, writes the ICC profile of the decoded image to " 194 "this file.", 195 &icc_out, &ParseString, 2); 196 197 cmdline->AddOptionValue( 198 '\0', "orig_icc_out", "FILENAME", 199 "If specified, writes the ICC profile of the original image to " 200 "this file\n" 201 " This can be different from the ICC profile of the " 202 "decoded image if --color_space was specified.", 203 &orig_icc_out, &ParseString, 2); 204 205 cmdline->AddOptionValue('\0', "metadata_out", "FILENAME", 206 "If specified, writes metadata info to a JSON " 207 "file. Used by the conformance test script", 208 &metadata_out, &ParseString, 2); 209 210 cmdline->AddOptionValue('\0', "background", "#NNNNNN", 211 "Specifies the background color for the " 212 "--alpha_blend option. Recognized values are " 213 "'black', 'white' (default), or '#NNNNNN'", 214 &background_spec, &ParseString, 2); 215 216 cmdline->AddOptionFlag('\0', "alpha_blend", 217 "Blends alpha channel with the color image using " 218 "background color specified by --background " 219 "(default is white).", 220 &alpha_blend, &SetBooleanTrue, 2); 221 222 cmdline->AddOptionFlag('\0', "print_read_bytes", 223 "Print total number of decoded bytes.", 224 &print_read_bytes, &SetBooleanTrue, 2); 225 } 226 227 // Validate the passed arguments, checking whether all passed options are 228 // compatible. Returns whether the validation was successful. 229 bool ValidateArgs(const CommandLineParser& cmdline) { 230 if (file_in == nullptr) { 231 fprintf(stderr, "Missing INPUT filename.\n"); 232 return false; 233 } 234 if (num_threads < -1) { 235 fprintf( 236 stderr, 237 "Invalid flag value for --num_threads: must be -1, 0 or positive.\n"); 238 return false; 239 } 240 return true; 241 } 242 243 const char* file_in = nullptr; 244 const char* file_out = nullptr; 245 std::string output_format; 246 bool version = false; 247 bool verbose = false; 248 size_t num_reps = 1; 249 bool disable_output = false; 250 int32_t num_threads = -1; 251 int bits_per_sample = -1; 252 double display_nits = 0.0; 253 std::string color_space; 254 uint32_t downsampling = 0; 255 bool allow_partial_files = false; 256 bool pixels_to_jpeg = false; 257 size_t jpeg_quality = 95; 258 bool use_sjpeg = false; 259 bool render_spotcolors = true; 260 bool output_extra_channels = false; 261 bool output_frames = false; 262 std::string preview_out; 263 std::string icc_out; 264 std::string orig_icc_out; 265 std::string metadata_out; 266 std::string background_spec = "white"; 267 bool alpha_blend = false; 268 bool print_read_bytes = false; 269 bool quiet = false; 270 // References (ids) of specific options to check if they were matched. 271 CommandLineParser::OptionId opt_bits_per_sample_id = -1; 272 CommandLineParser::OptionId opt_jpeg_quality_id = -1; 273 }; 274 275 } // namespace tools 276 } // namespace jpegxl 277 278 namespace { 279 280 bool WriteOptionalOutput(const std::string& filename, 281 const std::vector<uint8_t>& bytes) { 282 if (filename.empty() || bytes.empty()) { 283 return true; 284 } 285 return jpegxl::tools::WriteFile(filename, bytes); 286 } 287 288 std::string Filename(const std::string& filename, const std::string& extension, 289 int layer_index, int frame_index, int num_layers, 290 int num_frames) { 291 if (filename == "-") return "-"; 292 auto digits = [](int n) { return 1 + static_cast<int>(std::log10(n)); }; 293 std::string out = filename; 294 if (num_frames > 1) { 295 std::vector<char> buf(2 + digits(num_frames)); 296 snprintf(buf.data(), buf.size(), "-%0*d", digits(num_frames), frame_index); 297 out.append(buf.data()); 298 } 299 if (num_layers > 1 && layer_index > 0) { 300 std::vector<char> buf(4 + digits(num_layers)); 301 snprintf(buf.data(), buf.size(), "-ec%0*d", digits(num_layers), 302 layer_index); 303 out.append(buf.data()); 304 } 305 if (extension == ".ppm" && layer_index > 0) { 306 out.append(".pgm"); 307 } else if ((num_frames > 1) || (num_layers > 1 && layer_index > 0)) { 308 out.append(extension); 309 } 310 return out; 311 } 312 313 void AddFormatsWithAlphaChannel(std::vector<JxlPixelFormat>* formats) { 314 auto add_format = [&](JxlPixelFormat format) { 315 for (auto f : *formats) { 316 // NB: must reflect changes in JxlPixelFormat. 317 if (f.num_channels == format.num_channels && 318 f.data_type == format.data_type && 319 f.endianness == format.endianness && f.align == format.align) { 320 return; 321 } 322 } 323 formats->push_back(format); 324 }; 325 size_t num_formats = formats->size(); 326 for (size_t i = 0; i < num_formats; ++i) { 327 JxlPixelFormat format = (*formats)[i]; 328 if (format.num_channels == 1 || format.num_channels == 3) { 329 ++format.num_channels; 330 add_format(format); 331 } 332 } 333 } 334 335 bool ParseBackgroundColor(const std::string& background_desc, 336 float background[3]) { 337 if (background_desc == "black") { 338 background[0] = background[1] = background[2] = 0.0f; 339 return true; 340 } 341 if (background_desc == "white") { 342 background[0] = background[1] = background[2] = 1.0f; 343 return true; 344 } 345 if (background_desc.size() != 7 || background_desc[0] != '#') { 346 return false; 347 } 348 uint32_t color = std::stoi(background_desc.substr(1), nullptr, 16); 349 background[0] = ((color >> 16) & 0xff) * (1.0f / 255); 350 background[1] = ((color >> 8) & 0xff) * (1.0f / 255); 351 background[2] = (color & 0xff) * (1.0f / 255); 352 return true; 353 } 354 355 bool DecompressJxlReconstructJPEG(const jpegxl::tools::DecompressArgs& args, 356 const std::vector<uint8_t>& compressed, 357 void* runner, 358 std::vector<uint8_t>* jpeg_bytes, 359 jpegxl::tools::SpeedStats* stats) { 360 const double t0 = jxl::Now(); 361 jxl::extras::PackedPixelFile ppf; // for JxlBasicInfo 362 jxl::extras::JXLDecompressParams dparams; 363 dparams.allow_partial_input = args.allow_partial_files; 364 dparams.runner = JxlThreadParallelRunner; 365 dparams.runner_opaque = runner; 366 if (!jxl::extras::DecodeImageJXL(compressed.data(), compressed.size(), 367 dparams, nullptr, &ppf, jpeg_bytes)) { 368 return false; 369 } 370 const double t1 = jxl::Now(); 371 if (stats) { 372 stats->NotifyElapsed(t1 - t0); 373 stats->SetImageSize(ppf.info.xsize, ppf.info.ysize); 374 stats->SetFileSize(jpeg_bytes->size()); 375 } 376 return true; 377 } 378 379 bool DecompressJxlToPackedPixelFile( 380 const jpegxl::tools::DecompressArgs& args, 381 const std::vector<uint8_t>& compressed, 382 const std::vector<JxlPixelFormat>& accepted_formats, void* runner, 383 jxl::extras::PackedPixelFile* ppf, size_t* decoded_bytes, 384 jpegxl::tools::SpeedStats* stats) { 385 jxl::extras::JXLDecompressParams dparams; 386 dparams.max_downsampling = args.downsampling; 387 dparams.accepted_formats = accepted_formats; 388 dparams.display_nits = args.display_nits; 389 dparams.color_space = args.color_space; 390 dparams.render_spotcolors = args.render_spotcolors; 391 dparams.runner = JxlThreadParallelRunner; 392 dparams.runner_opaque = runner; 393 dparams.allow_partial_input = args.allow_partial_files; 394 if (args.bits_per_sample == 0) { 395 dparams.output_bitdepth.type = JXL_BIT_DEPTH_FROM_CODESTREAM; 396 } else if (args.bits_per_sample > 0) { 397 dparams.output_bitdepth.type = JXL_BIT_DEPTH_CUSTOM; 398 dparams.output_bitdepth.bits_per_sample = args.bits_per_sample; 399 } 400 const double t0 = jxl::Now(); 401 if (!jxl::extras::DecodeImageJXL(compressed.data(), compressed.size(), 402 dparams, decoded_bytes, ppf)) { 403 return false; 404 } 405 const double t1 = jxl::Now(); 406 if (stats) { 407 stats->NotifyElapsed(t1 - t0); 408 stats->SetImageSize(ppf->info.xsize, ppf->info.ysize); 409 } 410 return true; 411 } 412 413 } // namespace 414 415 int main(int argc, const char* argv[]) { 416 std::string version = jpegxl::tools::CodecConfigString(JxlDecoderVersion()); 417 jpegxl::tools::DecompressArgs args; 418 jpegxl::tools::CommandLineParser cmdline; 419 args.AddCommandLineOptions(&cmdline); 420 421 if (!cmdline.Parse(argc, argv)) { 422 // Parse already printed the actual error cause. 423 fprintf(stderr, "Use '%s -h' for more information\n", argv[0]); 424 return EXIT_FAILURE; 425 } 426 427 if (args.version) { 428 fprintf(stdout, "djxl %s\n", version.c_str()); 429 fprintf(stdout, "Copyright (c) the JPEG XL Project\n"); 430 return EXIT_SUCCESS; 431 } 432 if (!args.quiet) { 433 fprintf(stderr, "JPEG XL decoder %s\n", version.c_str()); 434 } 435 436 if (cmdline.HelpFlagPassed() || !args.file_in) { 437 cmdline.PrintHelp(); 438 return EXIT_SUCCESS; 439 } 440 441 if (!args.ValidateArgs(cmdline)) { 442 // ValidateArgs already printed the actual error cause. 443 fprintf(stderr, "Use '%s -h' for more information\n", argv[0]); 444 return EXIT_FAILURE; 445 } 446 447 std::vector<uint8_t> compressed; 448 // Reading compressed JPEG XL input 449 if (!jpegxl::tools::ReadFile(args.file_in, &compressed)) { 450 fprintf(stderr, "couldn't load %s\n", args.file_in); 451 return EXIT_FAILURE; 452 } 453 if (!args.quiet) { 454 cmdline.VerbosePrintf(1, "Read %" PRIuS " compressed bytes.\n", 455 compressed.size()); 456 } 457 458 if (!args.file_out && !args.disable_output) { 459 std::cerr 460 << "No output file specified and --disable_output flag not passed." 461 << std::endl; 462 return EXIT_FAILURE; 463 } 464 465 if (args.file_out && args.disable_output && !args.quiet) { 466 fprintf(stderr, 467 "Decoding will be performed, but the result will be discarded.\n"); 468 } 469 470 std::string filename_out; 471 std::string extension; 472 if (!args.output_format.empty()) extension = "." + args.output_format; 473 jxl::extras::Codec codec = jxl::extras::Codec::kUnknown; 474 if (args.file_out && !args.disable_output) { 475 filename_out = std::string(args.file_out); 476 codec = jxl::extras::CodecFromPath( 477 filename_out, /* bits_per_sample */ nullptr, &extension); 478 } 479 if (codec == jxl::extras::Codec::kEXR) { 480 std::string force_colorspace = "RGB_D65_SRG_Rel_Lin"; 481 if (!args.color_space.empty() && args.color_space != force_colorspace) { 482 fprintf(stderr, "Warning: colorspace ignored for EXR output\n"); 483 } 484 args.color_space = force_colorspace; 485 } 486 if (codec == jxl::extras::Codec::kPNM && extension != ".pfm" && 487 (args.opt_jpeg_quality_id < 0 || 488 !cmdline.GetOption(args.opt_jpeg_quality_id)->matched())) { 489 args.bits_per_sample = 0; 490 } 491 492 jpegxl::tools::SpeedStats stats; 493 size_t num_worker_threads = JxlThreadParallelRunnerDefaultNumWorkerThreads(); 494 { 495 int64_t flag_num_worker_threads = args.num_threads; 496 if (flag_num_worker_threads > -1) { 497 num_worker_threads = flag_num_worker_threads; 498 } 499 } 500 auto runner = JxlThreadParallelRunnerMake( 501 /*memory_manager=*/nullptr, num_worker_threads); 502 503 bool decode_to_pixels = (codec != jxl::extras::Codec::kJPG); 504 if (args.opt_jpeg_quality_id >= 0 && 505 (args.pixels_to_jpeg || 506 cmdline.GetOption(args.opt_jpeg_quality_id)->matched())) { 507 decode_to_pixels = true; 508 } 509 510 size_t num_reps = args.num_reps; 511 if (!decode_to_pixels) { 512 std::vector<uint8_t> bytes; 513 for (size_t i = 0; i < num_reps; ++i) { 514 if (!DecompressJxlReconstructJPEG(args, compressed, runner.get(), &bytes, 515 &stats)) { 516 if (bytes.empty()) { 517 if (!args.quiet) { 518 fprintf(stderr, 519 "Warning: could not decode losslessly to JPEG. Retrying " 520 "with --pixels_to_jpeg...\n"); 521 } 522 decode_to_pixels = true; 523 break; 524 } 525 return EXIT_FAILURE; 526 } 527 } 528 if (!bytes.empty()) { 529 if (!args.quiet) cmdline.VerbosePrintf(0, "Reconstructed to JPEG.\n"); 530 if (!filename_out.empty() && 531 !jpegxl::tools::WriteFile(filename_out, bytes)) { 532 return EXIT_FAILURE; 533 } 534 } 535 } 536 if (decode_to_pixels) { 537 std::vector<JxlPixelFormat> accepted_formats; 538 std::unique_ptr<jxl::extras::Encoder> encoder; 539 if (!filename_out.empty()) { 540 encoder = jxl::extras::Encoder::FromExtension(extension); 541 if (encoder == nullptr) { 542 if (extension.empty()) { 543 fprintf(stderr, 544 "couldn't detect output format, consider using " 545 "--output_format.\n"); 546 } else { 547 fprintf(stderr, "can't decode to the file extension '%s'.\n", 548 extension.c_str()); 549 } 550 return EXIT_FAILURE; 551 } 552 accepted_formats = encoder->AcceptedFormats(); 553 if (args.alpha_blend) { 554 AddFormatsWithAlphaChannel(&accepted_formats); 555 } 556 } 557 if (filename_out.empty()) { 558 // Decoding to pixels only, fill in float pixel formats 559 for (const uint32_t num_channels : {1, 2, 3, 4}) { 560 for (JxlEndianness endianness : {JXL_BIG_ENDIAN, JXL_LITTLE_ENDIAN}) { 561 accepted_formats.push_back(JxlPixelFormat{ 562 num_channels, JXL_TYPE_FLOAT, endianness, /*align=*/0}); 563 } 564 } 565 } 566 jxl::extras::PackedPixelFile ppf; 567 size_t decoded_bytes = 0; 568 for (size_t i = 0; i < num_reps; ++i) { 569 if (!DecompressJxlToPackedPixelFile(args, compressed, accepted_formats, 570 runner.get(), &ppf, &decoded_bytes, 571 &stats)) { 572 fprintf(stderr, "DecompressJxlToPackedPixelFile failed\n"); 573 return EXIT_FAILURE; 574 } 575 } 576 if (!args.quiet) cmdline.VerbosePrintf(0, "Decoded to pixels.\n"); 577 if (args.print_read_bytes) { 578 fprintf(stderr, "Decoded bytes: %" PRIuS "\n", decoded_bytes); 579 } 580 // When --disable_output was parsed, `filename_out` is empty and we don't 581 // need to write files. 582 if (encoder) { 583 if (args.alpha_blend) { 584 float background[3]; 585 if (!ParseBackgroundColor(args.background_spec, background)) { 586 fprintf(stderr, "Invalid background color %s\n", 587 args.background_spec.c_str()); 588 } 589 AlphaBlend(&ppf, background); 590 } 591 std::ostringstream os; 592 os << args.jpeg_quality; 593 encoder->SetOption("q", os.str()); 594 if (args.use_sjpeg) { 595 encoder->SetOption("jpeg_encoder", "sjpeg"); 596 } 597 jxl::extras::EncodedImage encoded_image; 598 if (!args.quiet) cmdline.VerbosePrintf(2, "Encoding decoded image\n"); 599 if (!encoder->Encode(ppf, &encoded_image, nullptr)) { 600 fprintf(stderr, "Encode failed\n"); 601 return EXIT_FAILURE; 602 } 603 size_t nlayers = args.output_extra_channels 604 ? 1 + encoded_image.extra_channel_bitstreams.size() 605 : 1; 606 size_t nframes = args.output_frames ? encoded_image.bitstreams.size() : 1; 607 for (size_t i = 0; i < nlayers; ++i) { 608 for (size_t j = 0; j < nframes; ++j) { 609 const std::vector<uint8_t>& bitstream = 610 (i == 0 ? encoded_image.bitstreams[j] 611 : encoded_image.extra_channel_bitstreams[i - 1][j]); 612 std::string fn = 613 Filename(filename_out, extension, i, j, nlayers, nframes); 614 if (!jpegxl::tools::WriteFile(fn, bitstream)) { 615 return EXIT_FAILURE; 616 } 617 if (!args.quiet) 618 cmdline.VerbosePrintf(1, "Wrote output to %s\n", fn.c_str()); 619 } 620 } 621 if (!WriteOptionalOutput(args.preview_out, 622 encoded_image.preview_bitstream) || 623 !WriteOptionalOutput(args.icc_out, ppf.icc) || 624 !WriteOptionalOutput(args.orig_icc_out, ppf.orig_icc) || 625 !WriteOptionalOutput(args.metadata_out, encoded_image.metadata)) { 626 return EXIT_FAILURE; 627 } 628 } 629 } 630 if (!args.quiet) { 631 stats.Print(num_worker_threads); 632 } 633 634 return EXIT_SUCCESS; 635 }