libjxl

FORK: libjxl patches used on blog
git clone https://git.neptards.moe/blog/libjxl.git
Log | Files | Refs | Submodules | README | LICENSE

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 }