libjxl

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

cjxl_main.cc (49204B)


      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 // Note: This encoder binary does extensive flag-validity checking (in
      7 // order to produce meaningful error messages), and on top of that
      8 // checks all libjxl C API call return values. The downside of this
      9 // vs. libjxl providing meaningful error messages is that a change to
     10 // the accepted range of a flag-specified parameter in libjxl will
     11 // also require a change to the range-check here. The advantage is
     12 // that this minimizes the size of libjxl.
     13 
     14 #include <jxl/codestream_header.h>
     15 #include <jxl/encode.h>
     16 #include <jxl/thread_parallel_runner.h>
     17 #include <jxl/thread_parallel_runner_cxx.h>
     18 #include <jxl/types.h>
     19 
     20 #include <algorithm>
     21 #include <cerrno>
     22 #include <cstdint>
     23 #include <cstdio>
     24 #include <cstdlib>
     25 #include <cstring>
     26 #include <functional>
     27 #include <iostream>
     28 #include <memory>
     29 #include <string>
     30 #include <utility>
     31 #include <vector>
     32 
     33 #include "lib/extras/dec/color_hints.h"
     34 #include "lib/extras/dec/decode.h"
     35 #include "lib/extras/dec/pnm.h"
     36 #include "lib/extras/enc/jxl.h"
     37 #include "lib/extras/packed_image.h"
     38 #include "lib/extras/time.h"
     39 #include "lib/jxl/base/c_callback_support.h"
     40 #include "lib/jxl/base/common.h"
     41 #include "lib/jxl/base/exif.h"
     42 #include "lib/jxl/base/override.h"
     43 #include "lib/jxl/base/printf_macros.h"
     44 #include "lib/jxl/base/span.h"
     45 #include "lib/jxl/base/status.h"
     46 #include "tools/args.h"
     47 #include "tools/cmdline.h"
     48 #include "tools/codec_config.h"
     49 #include "tools/file_io.h"
     50 #include "tools/speed_stats.h"
     51 
     52 namespace jpegxl {
     53 namespace tools {
     54 
     55 namespace {
     56 inline bool ParsePhotonNoiseParameter(const char* arg, float* out) {
     57   return ParseFloat(arg, out) && *out >= 0;
     58 }
     59 inline bool ParseIntensityTarget(const char* arg, float* out) {
     60   return ParseFloat(arg, out) && *out > 0;
     61 }
     62 }  // namespace
     63 
     64 enum CjxlRetCode : int {
     65   OK = 0,
     66   ERR_PARSE,
     67   ERR_INVALID_ARG,
     68   ERR_LOAD_INPUT,
     69   ERR_INVALID_INPUT,
     70   ERR_ENCODING,
     71   ERR_CONTAINER,
     72   ERR_WRITE,
     73   DROPPED_JBRD,
     74 };
     75 
     76 struct CompressArgs {
     77   // CompressArgs() = default;
     78   void AddCommandLineOptions(CommandLineParser* cmdline) {
     79     std::string input_help("the input can be ");
     80     if (jxl::extras::CanDecode(jxl::extras::Codec::kPNG)) {
     81       input_help.append("PNG, APNG, ");
     82     }
     83     if (jxl::extras::CanDecode(jxl::extras::Codec::kGIF)) {
     84       input_help.append("GIF, ");
     85     }
     86     if (jxl::extras::CanDecode(jxl::extras::Codec::kJPG)) {
     87       input_help.append("JPEG, ");
     88     } else {
     89       input_help.append("JPEG (lossless recompression only), ");
     90     }
     91     if (jxl::extras::CanDecode(jxl::extras::Codec::kEXR)) {
     92       input_help.append("EXR, ");
     93     }
     94     input_help.append("PPM, PFM, PAM, PGX, or JXL");
     95     // Positional arguments.
     96     cmdline->AddPositionalOption("INPUT", /* required = */ true, input_help,
     97                                  &file_in);
     98     cmdline->AddPositionalOption("OUTPUT", /* required = */ true,
     99                                  "the compressed JXL output file", &file_out);
    100 
    101     // Flags.
    102 
    103     cmdline->AddHelpText("\nBasic options:", 0);
    104 
    105     // Target distance/size/bpp
    106     opt_distance_id = cmdline->AddOptionValue(
    107         'd', "distance", "DISTANCE",
    108         "Target visual distance in JND units, lower = higher quality.\n"
    109         "    0.0 = mathematically lossless. Default for already-lossy input "
    110         "(JPEG/GIF).\n"
    111         "    1.0 = visually lossless. Default for other input.\n"
    112         "    Recommended range: 0.5 .. 3.0. Allowed range: 0.0 ... 25.0. "
    113         "Mutually exclusive with --quality.",
    114         &distance, &ParseFloat);
    115 
    116     // High-level options
    117     opt_quality_id = cmdline->AddOptionValue(
    118         'q', "quality", "QUALITY",
    119         "Quality setting, higher value = higher quality. This is internally "
    120         "mapped to --distance.\n"
    121         "    100 = mathematically lossless. 90 = visually lossless.\n"
    122         "    Quality values roughly match libjpeg quality.\n"
    123         "    Recommended range: 68 .. 96. Allowed range: 0 .. 100. Mutually "
    124         "exclusive with --distance.",
    125         &quality, &ParseFloat);
    126 
    127     cmdline->AddOptionValue(
    128         'e', "effort", "EFFORT",
    129         "Encoder effort setting. Range: 1 .. 10.\n"
    130         "    Default: 7. Higher numbers allow more computation "
    131         "at the expense of time.\n"
    132         "    For lossless, generally it will produce smaller files.\n"
    133         "    For lossy, higher effort should more accurately reach "
    134         "the target quality.",
    135         &effort, &ParseUnsigned);
    136 
    137     cmdline->AddOptionFlag('V', "version",
    138                            "Print encoder library version number and exit.",
    139                            &version, &SetBooleanTrue);
    140     cmdline->AddOptionFlag('\0', "quiet", "Be more silent", &quiet,
    141                            &SetBooleanTrue);
    142     cmdline->AddOptionFlag('v', "verbose",
    143                            "Verbose output; can be repeated and also applies "
    144                            "to help (!).",
    145                            &verbose, &SetBooleanTrue);
    146 
    147     cmdline->AddHelpText("\nAdvanced options:", 1);
    148 
    149     opt_alpha_distance_id = cmdline->AddOptionValue(
    150         'a', "alpha_distance", "A_DISTANCE",
    151         "Target visual distance for the alpha channel, lower = higher "
    152         "quality.\n"
    153         "    0.0 = mathematically lossless. 1.0 = visually lossless.\n"
    154         "    Default is 0.\n"
    155         "    Recommended range: 0.5 .. 3.0. Allowed range: 0.0 ... 25.0.",
    156         &alpha_distance, &ParseFloat, 1);
    157 
    158     cmdline->AddOptionFlag('p', "progressive",
    159                            "Enable (more) progressive/responsive decoding.",
    160                            &progressive, &SetBooleanTrue, 1);
    161 
    162     cmdline->AddOptionValue(
    163         '\0', "group_order", "0|1",
    164         "Order in which 256x256 groups are stored "
    165         "in the codestream for progressive rendering.\n"
    166         "    0 = scanline order, 1 = center-first order. Default: 0.",
    167         &group_order, &ParseOverride, 1);
    168 
    169     cmdline->AddOptionValue(
    170         '\0', "container", "0|1",
    171         "0 = Avoid the container format unless it is needed (default)\n"
    172         "    1 = Force using the container format even if it is not needed.",
    173         &container, &ParseOverride, 1);
    174 
    175     cmdline->AddOptionValue('\0', "compress_boxes", "0|1",
    176                             "Disable/enable Brotli compression for metadata "
    177                             "boxes. Default is 1 (enabled).",
    178                             &compress_boxes, &ParseOverride, 1);
    179 
    180     cmdline->AddOptionValue(
    181         '\0', "brotli_effort", "B_EFFORT",
    182         "Brotli effort setting. Range: 0 .. 11.\n"
    183         "    Default: 9. Higher number is more effort (slower).",
    184         &brotli_effort, &ParseUnsigned, 1);
    185 
    186     cmdline->AddOptionValue(
    187         'm', "modular", "0|1",
    188         "Use modular mode (default = encoder chooses, 0 = enforce VarDCT, "
    189         "1 = enforce modular mode).",
    190         &modular, &ParseOverride, 1);
    191 
    192     // JPEG modes: parallel Brunsli, pixels to JPEG, or JPEG to Brunsli
    193     opt_lossless_jpeg_id = cmdline->AddOptionValue(
    194         'j', "lossless_jpeg", "0|1",
    195         "If the input is JPEG, losslessly transcode JPEG, "
    196         "rather than using reencode pixels. Default is 1 (losslessly "
    197         "transcode)",
    198         &lossless_jpeg, &ParseSigned, 1);
    199 
    200     cmdline->AddOptionValue(
    201         '\0', "num_threads", "N",
    202         "Number of worker threads (-1 == use machine default, "
    203         "0 == do not use multithreading).",
    204         &num_threads, &ParseSigned, 1);
    205 
    206     cmdline->AddOptionValue(
    207         '\0', "photon_noise_iso", "ISO_FILM_SPEED",
    208         "Adds noise to the image emulating photographic film or sensor noise.\n"
    209         "    Higher number = grainier image, e.g. 100 gives a low amount of "
    210         "noise,\n"
    211         "    3200 gives a lot of noise. Default is 0.",
    212         &photon_noise_iso, &ParsePhotonNoiseParameter, 1);
    213 
    214     cmdline->AddOptionValue(
    215         '\0', "intensity_target", "N",
    216         "Upper bound on the intensity level present in the image, in nits.\n"
    217         "    Default is 0, which means 'choose a sensible default "
    218         "value based on the color encoding.",
    219         &intensity_target, &ParseIntensityTarget, 1);
    220 
    221     cmdline->AddOptionValue(
    222         'x', "dec-hints", "key=value",
    223         "This is useful for 'raw' formats like PPM that cannot store "
    224         "colorspace information\n"
    225         "    and metadata, or to strip or modify metadata in formats that do.\n"
    226         "    The key 'color_space' indicates an enumerated ColorEncoding, for "
    227         "example:\n"
    228         "      -x color_space=RGB_D65_SRG_Per_SRG is sRGB with perceptual "
    229         "rendering intent\n"
    230         "      -x color_space=RGB_D65_202_Rel_PeQ is Rec.2100 PQ with relative "
    231         "rendering intent\n"
    232         "    The key 'icc_pathname' refers to a binary file containing an ICC "
    233         "profile.\n"
    234         "    The keys 'exif', 'xmp', and 'jumbf' refer to a binary file "
    235         "containing metadata;\n"
    236         "    existing metadata of the same type will be overwritten.\n"
    237         "    Specific metadata can be stripped using e.g. -x strip=exif."
    238         "    Stripping metadata when losslessly recompression JPEGs only works "
    239         "    without reconstruction, hence `--allow_jpeg_reconstruction=0` "
    240         "    must be passed in this case.",
    241         &color_hints_proxy, &ParseAndAppendKeyValue<ColorHintsProxy>, 1);
    242 
    243     cmdline->AddHelpText("\nExpert options:", 2);
    244 
    245     cmdline->AddOptionValue(
    246         '\0', "allow_jpeg_reconstruction", "0|1",
    247         ("If --lossless_jpeg=1, store JPEG reconstruction "
    248          "metadata in the JPEG XL container.\n"
    249          "    This allows reconstruction of the JPEG codestream. Default: 1."),
    250         &allow_jpeg_reconstruction, &ParseSigned, 2);
    251 
    252     cmdline->AddOptionValue('\0', "codestream_level", "K",
    253                             "The codestream level. Either `-1`, `5` or `10`.",
    254                             &codestream_level, &ParseInt64, 2);
    255 
    256     cmdline->AddOptionValue('\0', "faster_decoding", "0|1|2|3|4",
    257                             "0 = default, higher values improve decode speed "
    258                             "at the expense of quality or density.",
    259                             &faster_decoding, &ParseUnsigned, 2);
    260 
    261     cmdline->AddOptionValue('\0', "premultiply", "-1|0|1",
    262                             "Force premultiplied (associated) alpha.",
    263                             &premultiply, &ParseSigned, 2);
    264 
    265     cmdline->AddOptionValue('\0', "keep_invisible", "0|1",
    266                             "disable/enable preserving color of invisible "
    267                             "pixels (default: 1 if lossless, 0 if lossy).",
    268                             &keep_invisible, &ParseOverride, 2);
    269 
    270     cmdline->AddOptionValue(
    271         '\0', "center_x", "-1..XSIZE",
    272         "Determines the horizontal position of center for the center-first "
    273         "group order.\n"
    274         "    Default -1 means 'middle of the image', "
    275         "values [0..xsize) set this to a particular coordinate.",
    276         &center_x, &ParseInt64, 2);
    277 
    278     cmdline->AddOptionValue(
    279         '\0', "center_y", "-1..YSIZE",
    280         "Determines the vertical position of center for the center-first "
    281         "group order.\n"
    282         "    Default -1 means 'middle of the image', "
    283         "values [0..ysize) set this to a particular coordinate.",
    284         &center_y, &ParseInt64, 2);
    285 
    286     // Flags.
    287     cmdline->AddOptionFlag('\0', "progressive_ac",
    288                            "Use the progressive mode for AC.", &progressive_ac,
    289                            &SetBooleanTrue, 2);
    290 
    291     cmdline->AddOptionFlag(
    292         '\0', "qprogressive_ac",
    293         "Use the progressive mode for AC with shift quantization.",
    294         &qprogressive_ac, &SetBooleanTrue, 2);
    295 
    296     cmdline->AddOptionValue(
    297         '\0', "progressive_dc", "num_dc_frames",
    298         "Progressive-DC setting. Valid values are: -1, 0, 1, 2.",
    299         &progressive_dc, &ParseInt64, 2);
    300 
    301     cmdline->AddOptionValue('\0', "resampling", "-1|1|2|4|8",
    302                             "Resampling for color channels. Default of -1 "
    303                             "applies resampling only for very low quality.\n"
    304                             "    1 = downsampling (1x1), 2 = 2x2 downsampling, "
    305                             "4 = 4x4 downsampling, 8 = 8x8 downsampling.",
    306                             &resampling, &ParseInt64, 2);
    307 
    308     cmdline->AddOptionValue('\0', "ec_resampling", "-1|1|2|4|8",
    309                             "Resampling for extra channels. Same as "
    310                             "--resampling but for extra channels like alpha.",
    311                             &ec_resampling, &ParseInt64, 2);
    312 
    313     cmdline->AddOptionFlag('\0', "already_downsampled",
    314                            "Do not downsample before encoding, "
    315                            "but still signal that the decoder should upsample.",
    316                            &already_downsampled, &SetBooleanTrue, 2);
    317 
    318     cmdline->AddOptionValue(
    319         '\0', "upsampling_mode", "-1|0|1",
    320         "Upsampling mode the decoder should use. Mostly useful in combination "
    321         "with --already_downsampled. Value -1 means default (non-separable "
    322         "upsampling), 0 means nearest neighbor (useful for pixel art)",
    323         &upsampling_mode, &ParseInt64, 2);
    324 
    325     cmdline->AddOptionValue(
    326         '\0', "epf", "-1|0|1|2|3",
    327         "Edge preserving filter level, 0-3. "
    328         "Default -1 means encoder chooses, 0-3 set a strength.",
    329         &epf, &ParseInt64, 2);
    330 
    331     cmdline->AddOptionValue('\0', "gaborish", "0|1",
    332                             "Force disable/enable the gaborish filter. Default "
    333                             "is 'encoder chooses'",
    334                             &gaborish, &ParseOverride, 2);
    335 
    336     cmdline->AddOptionValue('\0', "override_bitdepth", "BITDEPTH",
    337                             "Default is zero (use the input image bit depth); "
    338                             "if nonzero, override the bit depth",
    339                             &override_bitdepth, &ParseUnsigned, 2);
    340 
    341     cmdline->AddHelpText("\nOptions for experimentation / benchmarking:", 3);
    342 
    343     cmdline->AddOptionValue('\0', "noise", "0|1",
    344                             "Force disable/enable adaptive noise generation "
    345                             "(experimental). Default "
    346                             "is 'encoder chooses'",
    347                             &noise, &ParseOverride, 3);
    348 
    349     cmdline->AddOptionValue(
    350         '\0', "jpeg_reconstruction_cfl", "0|1",
    351         "Enable/disable chroma-from-luma (CFL) for lossless "
    352         "JPEG reconstruction.",
    353         &jpeg_reconstruction_cfl, &ParseOverride, 3);
    354 
    355     cmdline->AddOptionValue('\0', "num_reps", "N",
    356                             "How many times to compress. (For benchmarking).",
    357                             &num_reps, &ParseUnsigned, 3);
    358 
    359     cmdline->AddOptionFlag('\0', "streaming_input",
    360                            "Enable streaming processing of the input file "
    361                            "(works only for PPM and PGM input files).",
    362                            &streaming_input, &SetBooleanTrue, 3);
    363     cmdline->AddOptionFlag('\0', "streaming_output",
    364                            "Enable incremental writing of the output file.",
    365                            &streaming_output, &SetBooleanTrue, 3);
    366     cmdline->AddOptionFlag('\0', "disable_output",
    367                            "No output file will be written (for benchmarking)",
    368                            &disable_output, &SetBooleanTrue, 3);
    369 
    370     cmdline->AddOptionValue(
    371         '\0', "dots", "0|1",
    372         "Force disable/enable dots generation. "
    373         "(not provided = default, 0 = disable, 1 = enable).",
    374         &dots, &ParseOverride, 3);
    375 
    376     cmdline->AddOptionValue(
    377         '\0', "patches", "0|1",
    378         "Force disable/enable patches generation. "
    379         "(not provided = default, 0 = disable, 1 = enable).",
    380         &patches, &ParseOverride, 3);
    381 
    382     cmdline->AddOptionValue(
    383         '\0', "frame_indexing", "INDICES",
    384         // TODO(tfish): Add a more convenient vanilla alternative.
    385         "INDICES is of the form '^(0*|1[01]*)'. The i-th position indicates "
    386         "whether the\n"
    387         "    i-th frame will be indexed in the frame index box.",
    388         &frame_indexing, &ParseString, 3);
    389 
    390     cmdline->AddOptionFlag('\0', "allow_expert_options",
    391                            "Allow specifying advanced options; this allows "
    392                            "setting effort to 11, for\n"
    393                            "    somewhat better lossless compression at the "
    394                            "cost of a massive speed hit.",
    395                            &allow_expert_options, &SetBooleanTrue, 3);
    396 
    397     cmdline->AddHelpText("\nModular mode options:", 4);
    398 
    399     // modular mode options
    400     cmdline->AddOptionValue(
    401         'I', "iterations", "PERCENT",
    402         "Percentage of pixels used to learn MA trees. Higher values use\n"
    403         "    more encoder memory and can result in better compression. Default "
    404         "of -1 means\n"
    405         "    the encoder chooses. Zero means no MA trees are used.",
    406         &modular_ma_tree_learning_percent, &ParseFloat, 4);
    407 
    408     cmdline->AddOptionValue(
    409         'C', "modular_colorspace", "K",
    410         ("Color transform: -1 = default (try several per group, depending\n"
    411          "    on effort), 0 = RGB (none), 1-41 = fixed RCT (6 = YCoCg)."),
    412         &modular_colorspace, &ParseInt64, 4);
    413 
    414     opt_modular_group_size_id = cmdline->AddOptionValue(
    415         'g', "modular_group_size", "K",
    416         "Group size: -1 = default (let the encoder choose),\n"
    417         "    0 = 128x128, 1 = 256x256, 2 = 512x512, 3 = 1024x1024.",
    418         &modular_group_size, &ParseInt64, 4);
    419 
    420     cmdline->AddOptionValue(
    421         'P', "modular_predictor", "K",
    422         "Predictor(s) to use: 0=zero, 1=left, 2=top, 3=avg0, 4=select,\n"
    423         "    5=gradient, 6=weighted, 7=topright, 8=topleft, 9=leftleft, "
    424         "10=avg1, 11=avg2, 12=avg3,\n"
    425         "    13=toptop predictive average, 14=mix 5 and 6, 15=mix everything.\n"
    426         "    Default is 14 at effort < 9 and 15 at effort 9-10.",
    427         &modular_predictor, &ParseInt64, 4);
    428 
    429     cmdline->AddOptionValue(
    430         'E', "modular_nb_prev_channels", "K",
    431         "Number of extra (previous-channel) MA tree properties to use.",
    432         &modular_nb_prev_channels, &ParseInt64, 4);
    433 
    434     cmdline->AddOptionValue(
    435         '\0', "modular_palette_colors", "K",
    436         "Use palette if number of colors is smaller than or equal to this.",
    437         &modular_palette_colors, &ParseInt64, 4);
    438 
    439     cmdline->AddOptionFlag(
    440         '\0', "modular_lossy_palette",
    441         "Use delta palette in a lossy way; it is recommended to also\n"
    442         "    set --modular_palette_colors=0 with this "
    443         "option to use the default palette only.",
    444         &modular_lossy_palette, &SetBooleanTrue, 4);
    445 
    446     cmdline->AddOptionValue('X', "pre-compact", "PERCENT",
    447                             "Use global channel palette if the number of "
    448                             "sample values is smaller\n"
    449                             "    than this percentage of the nominal range. ",
    450                             &modular_channel_colors_global_percent, &ParseFloat,
    451                             4);
    452 
    453     cmdline->AddOptionValue(
    454         'Y', "post-compact", "PERCENT",
    455         "Use local (per-group) channel palette if the "
    456         "number of sample values is\n"
    457         "    smaller than this percentage of the nominal range.",
    458         &modular_channel_colors_group_percent, &ParseFloat, 4);
    459 
    460     opt_responsive_id =
    461         cmdline->AddOptionValue('R', "responsive", "K",
    462                                 "Do the Squeeze transform, 0=false, "
    463                                 "1=true (default: 1 if lossy, 0 if lossless)",
    464                                 &responsive, &ParseInt64, 4);
    465   }
    466 
    467   // Common flags.
    468   const char* file_in = nullptr;
    469   const char* file_out = nullptr;
    470 
    471   bool version = false;
    472   jxl::Override container = jxl::Override::kDefault;
    473   bool quiet = false;
    474   bool disable_output = false;
    475 
    476   jxl::Override print_profile = jxl::Override::kDefault;
    477   bool streaming_input = false;
    478   bool streaming_output = false;
    479 
    480   bool verbose = false;
    481 
    482   // Decoding source image flags
    483   ColorHintsProxy color_hints_proxy;
    484 
    485   // JXL flags
    486   size_t override_bitdepth = 0;
    487   size_t num_reps = 1;
    488   int32_t num_threads = -1;
    489   float intensity_target = 0;
    490 
    491   // Whether to perform lossless transcoding with kVarDCT or kJPEG encoding.
    492   // If true, attempts to load JPEG coefficients instead of pixels.
    493   // Reset to false if input image is not a JPEG.
    494   JXL_BOOL lossless_jpeg = JXL_TRUE;
    495 
    496   JXL_BOOL allow_jpeg_reconstruction = JXL_TRUE;
    497 
    498   float quality = -1001.f;  // Default to lossless if input is already lossy,
    499                             // or to VarDCT otherwise.
    500   bool progressive = false;
    501   bool progressive_ac = false;
    502   bool qprogressive_ac = false;
    503   bool modular_lossy_palette = false;
    504   int64_t progressive_dc = -1;
    505   int64_t upsampling_mode = -1;
    506   int32_t premultiply = -1;
    507   bool already_downsampled = false;
    508   jxl::Override jpeg_reconstruction_cfl = jxl::Override::kDefault;
    509   jxl::Override modular = jxl::Override::kDefault;
    510   jxl::Override keep_invisible = jxl::Override::kDefault;
    511   jxl::Override dots = jxl::Override::kDefault;
    512   jxl::Override patches = jxl::Override::kDefault;
    513   jxl::Override gaborish = jxl::Override::kDefault;
    514   jxl::Override group_order = jxl::Override::kDefault;
    515   jxl::Override compress_boxes = jxl::Override::kDefault;
    516   jxl::Override noise = jxl::Override::kDefault;
    517 
    518   bool allow_expert_options = false;
    519 
    520   size_t faster_decoding = 0;
    521   int64_t resampling = -1;
    522   int64_t ec_resampling = -1;
    523   int64_t epf = -1;
    524   int64_t center_x = -1;
    525   int64_t center_y = -1;
    526   int64_t modular_group_size = -1;
    527   int64_t modular_predictor = -1;
    528   int64_t modular_colorspace = -1;
    529   float modular_channel_colors_global_percent = -1.f;
    530   float modular_channel_colors_group_percent = -1.f;
    531   int64_t modular_palette_colors = -1;
    532   int64_t modular_nb_prev_channels = -1;
    533   float modular_ma_tree_learning_percent = -1.f;
    534   float photon_noise_iso = 0;
    535   int64_t codestream_level = -1;
    536   int64_t responsive = -1;
    537   float distance = 1.0;
    538   float alpha_distance = 1.0;
    539   size_t effort = 7;
    540   size_t brotli_effort = 9;
    541   std::string frame_indexing;
    542 
    543   // References (ids) of specific options to check if they were matched.
    544   CommandLineParser::OptionId opt_lossless_jpeg_id = -1;
    545   CommandLineParser::OptionId opt_responsive_id = -1;
    546   CommandLineParser::OptionId opt_distance_id = -1;
    547   CommandLineParser::OptionId opt_alpha_distance_id = -1;
    548   CommandLineParser::OptionId opt_quality_id = -1;
    549   CommandLineParser::OptionId opt_modular_group_size_id = -1;
    550 };
    551 
    552 const char* ModeFromArgs(const CompressArgs& args) {
    553   if (FROM_JXL_BOOL(args.lossless_jpeg)) return "JPEG";
    554   if (args.modular == jxl::Override::kOn || args.distance == 0)
    555     return "Modular";
    556   return "VarDCT";
    557 }
    558 
    559 std::string DistanceFromArgs(const CompressArgs& args) {
    560   char buf[100];
    561   if (FROM_JXL_BOOL(args.lossless_jpeg)) {
    562     snprintf(buf, sizeof(buf), "lossless transcode");
    563   } else if (args.distance == 0) {
    564     snprintf(buf, sizeof(buf), "lossless");
    565   } else {
    566     snprintf(buf, sizeof(buf), "d%.3f", args.distance);
    567   }
    568   return buf;
    569 }
    570 
    571 void PrintMode(jxl::extras::PackedPixelFile& ppf, const double decode_mps,
    572                size_t num_bytes, const CompressArgs& args,
    573                jpegxl::tools::CommandLineParser& cmdline) {
    574   const char* mode = ModeFromArgs(args);
    575   const std::string distance = DistanceFromArgs(args);
    576   if (FROM_JXL_BOOL(args.lossless_jpeg)) {
    577     cmdline.VerbosePrintf(1, "Read JPEG image with %" PRIuS " bytes.\n",
    578                           num_bytes);
    579   } else if (num_bytes > 0) {
    580     cmdline.VerbosePrintf(
    581         1, "Read %" PRIuS "x%" PRIuS " image, %" PRIuS " bytes, %.1f MP/s\n",
    582         static_cast<size_t>(ppf.info.xsize),
    583         static_cast<size_t>(ppf.info.ysize), num_bytes, decode_mps);
    584   }
    585   cmdline.VerbosePrintf(
    586       0, "Encoding [%s%s, %s, effort: %" PRIuS,
    587       (args.container == jxl::Override::kOn ? "Container | " : ""), mode,
    588       distance.c_str(), args.effort);
    589   if (args.container == jxl::Override::kOn) {
    590     if (FROM_JXL_BOOL(args.lossless_jpeg) &&
    591         FROM_JXL_BOOL(args.allow_jpeg_reconstruction))
    592       cmdline.VerbosePrintf(0, " | JPEG reconstruction data");
    593     if (!ppf.metadata.exif.empty()) {
    594       cmdline.VerbosePrintf(0, " | %" PRIuS "-byte Exif",
    595                             ppf.metadata.exif.size());
    596     }
    597     if (!ppf.metadata.xmp.empty()) {
    598       cmdline.VerbosePrintf(0, " | %" PRIuS "-byte XMP",
    599                             ppf.metadata.xmp.size());
    600     }
    601     if (!ppf.metadata.jumbf.empty()) {
    602       cmdline.VerbosePrintf(0, " | %" PRIuS "-byte JUMBF",
    603                             ppf.metadata.jumbf.size());
    604     }
    605   }
    606   cmdline.VerbosePrintf(0, "]\n");
    607 }
    608 
    609 bool IsJPG(const std::vector<uint8_t>& image_data) {
    610   return (image_data.size() >= 2 && image_data[0] == 0xFF &&
    611           image_data[1] == 0xD8);
    612 }
    613 
    614 using flag_check_fn = std::function<std::string(int64_t)>;
    615 using flag_check_float_fn = std::function<std::string(float)>;
    616 
    617 template <typename T>
    618 void ProcessFlag(
    619     const char* flag_name, T flag_value,
    620     JxlEncoderFrameSettingId encoder_option,
    621     jxl::extras::JXLCompressParams* params,
    622     const flag_check_fn& flag_check = [](T x) { return std::string(); }) {
    623   std::string error = flag_check(flag_value);
    624   if (!error.empty()) {
    625     std::cerr << "Invalid flag value for --" << flag_name << ": " << error
    626               << std::endl;
    627     exit(EXIT_FAILURE);
    628   }
    629   params->options.emplace_back(
    630       jxl::extras::JXLOption(encoder_option, flag_value, 0));
    631 }
    632 
    633 void ProcessBoolFlag(jxl::Override flag_value,
    634                      JxlEncoderFrameSettingId encoder_option,
    635                      jxl::extras::JXLCompressParams* params) {
    636   if (flag_value != jxl::Override::kDefault) {
    637     int64_t value = flag_value == jxl::Override::kOn ? 1 : 0;
    638     params->options.emplace_back(encoder_option, value, 0);
    639   }
    640 }
    641 
    642 void SetDistanceFromFlags(CommandLineParser* cmdline, CompressArgs* args,
    643                           jxl::extras::JXLCompressParams* params,
    644                           const jxl::extras::Codec& codec) {
    645   bool distance_set = cmdline->GetOption(args->opt_distance_id)->matched();
    646   bool alpha_distance_set =
    647       cmdline->GetOption(args->opt_alpha_distance_id)->matched();
    648   bool quality_set = cmdline->GetOption(args->opt_quality_id)->matched();
    649   if ((distance_set && (args->distance != 0.0)) && args->lossless_jpeg) {
    650     std::cerr << "Must not set non-zero distance in combination with "
    651                  "--lossless_jpeg=1, which is set by default."
    652               << std::endl;
    653     exit(EXIT_FAILURE);
    654   }
    655   if ((quality_set && (args->quality != 100)) && args->lossless_jpeg) {
    656     std::cerr << "Must not set quality below 100 in combination with "
    657                  "--lossless_jpeg=1, which is set by default"
    658               << std::endl;
    659     exit(EXIT_FAILURE);
    660   }
    661   if (quality_set) {
    662     if (distance_set) {
    663       std::cerr << "Must not set both --distance and --quality." << std::endl;
    664       exit(EXIT_FAILURE);
    665     }
    666     args->distance = JxlEncoderDistanceFromQuality(args->quality);
    667     distance_set = true;
    668   }
    669 
    670   if (!distance_set) {
    671     bool lossy_input = (codec == jxl::extras::Codec::kJPG ||
    672                         codec == jxl::extras::Codec::kGIF);
    673     args->distance = lossy_input ? 0.0 : 1.0;
    674   } else if (args->distance > 0) {
    675     args->lossless_jpeg = JXL_FALSE;
    676   }
    677   params->distance = args->distance;
    678   params->alpha_distance = alpha_distance_set ? args->alpha_distance : 0;
    679 }
    680 
    681 void ProcessFlags(const jxl::extras::Codec codec,
    682                   const jxl::extras::PackedPixelFile& ppf,
    683                   const std::vector<uint8_t>* jpeg_bytes,
    684                   CommandLineParser* cmdline, CompressArgs* args,
    685                   jxl::extras::JXLCompressParams* params) {
    686   // Tuning flags.
    687   ProcessBoolFlag(args->modular, JXL_ENC_FRAME_SETTING_MODULAR, params);
    688   ProcessBoolFlag(args->keep_invisible, JXL_ENC_FRAME_SETTING_KEEP_INVISIBLE,
    689                   params);
    690   ProcessBoolFlag(args->dots, JXL_ENC_FRAME_SETTING_DOTS, params);
    691   ProcessBoolFlag(args->patches, JXL_ENC_FRAME_SETTING_PATCHES, params);
    692   ProcessBoolFlag(args->gaborish, JXL_ENC_FRAME_SETTING_GABORISH, params);
    693   ProcessBoolFlag(args->group_order, JXL_ENC_FRAME_SETTING_GROUP_ORDER, params);
    694   ProcessBoolFlag(args->noise, JXL_ENC_FRAME_SETTING_NOISE, params);
    695 
    696   params->allow_expert_options = args->allow_expert_options;
    697 
    698   if (!args->frame_indexing.empty()) {
    699     bool must_be_all_zeros = args->frame_indexing[0] != '1';
    700     for (char c : args->frame_indexing) {
    701       if (c == '1') {
    702         if (must_be_all_zeros) {
    703           std::cerr << "Invalid --frame_indexing. If the first character is "
    704                        "'0', all must be '0'."
    705                     << std::endl;
    706           exit(EXIT_FAILURE);
    707         }
    708       } else if (c != '0') {
    709         std::cerr << "Invalid --frame_indexing. Must match the pattern "
    710                      "'^(0*|1[01]*)$'."
    711                   << std::endl;
    712         exit(EXIT_FAILURE);
    713       }
    714     }
    715   }
    716 
    717   ProcessFlag(
    718       "effort", static_cast<int64_t>(args->effort),
    719       JXL_ENC_FRAME_SETTING_EFFORT, params, [args](int64_t x) -> std::string {
    720         if (args->allow_expert_options) {
    721           return (1 <= x && x <= 11) ? "" : "Valid range is {1, 2, ..., 11}.";
    722         } else {
    723           return (1 <= x && x <= 10) ? "" : "Valid range is {1, 2, ..., 10}.";
    724         }
    725       });
    726   ProcessFlag("brotli_effort", static_cast<int64_t>(args->brotli_effort),
    727               JXL_ENC_FRAME_SETTING_BROTLI_EFFORT, params,
    728               [](int64_t x) -> std::string {
    729                 return (-1 <= x && x <= 11)
    730                            ? ""
    731                            : "Valid range is {-1, 0, 1, ..., 11}.";
    732               });
    733   ProcessFlag(
    734       "epf", args->epf, JXL_ENC_FRAME_SETTING_EPF, params,
    735       [](int64_t x) -> std::string {
    736         return (-1 <= x && x <= 3) ? "" : "Valid range is {-1, 0, 1, 2, 3}.\n";
    737       });
    738   ProcessFlag("faster_decoding", static_cast<int64_t>(args->faster_decoding),
    739               JXL_ENC_FRAME_SETTING_DECODING_SPEED, params,
    740               [](int64_t x) -> std::string {
    741                 return (0 <= x && x <= 4) ? ""
    742                                           : "Valid range is {0, 1, 2, 3, 4}.\n";
    743               });
    744   ProcessFlag("resampling", args->resampling, JXL_ENC_FRAME_SETTING_RESAMPLING,
    745               params, [](int64_t x) -> std::string {
    746                 return (x == -1 || x == 1 || x == 2 || x == 4 || x == 8)
    747                            ? ""
    748                            : "Valid values are {-1, 1, 2, 4, 8}.\n";
    749               });
    750   ProcessFlag("ec_resampling", args->ec_resampling,
    751               JXL_ENC_FRAME_SETTING_EXTRA_CHANNEL_RESAMPLING, params,
    752               [](int64_t x) -> std::string {
    753                 return (x == -1 || x == 1 || x == 2 || x == 4 || x == 8)
    754                            ? ""
    755                            : "Valid values are {-1, 1, 2, 4, 8}.\n";
    756               });
    757   ProcessFlag("photon_noise_iso", args->photon_noise_iso,
    758               JXL_ENC_FRAME_SETTING_PHOTON_NOISE, params);
    759   ProcessFlag("already_downsampled",
    760               static_cast<int64_t>(args->already_downsampled),
    761               JXL_ENC_FRAME_SETTING_ALREADY_DOWNSAMPLED, params);
    762   if (args->already_downsampled) params->already_downsampled = args->resampling;
    763 
    764   SetDistanceFromFlags(cmdline, args, params, codec);
    765 
    766   if (args->group_order != jxl::Override::kOn &&
    767       (args->center_x != -1 || args->center_y != -1)) {
    768     std::cerr << "Invalid flag combination. Setting --center_x or --center_y "
    769               << "requires setting --group_order=1" << std::endl;
    770     exit(EXIT_FAILURE);
    771   }
    772   ProcessFlag("center_x", args->center_x,
    773               JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_X, params,
    774               [](int64_t x) -> std::string {
    775                 if (x < -1) {
    776                   return "Valid values are: -1 or [0 .. xsize).";
    777                 }
    778                 return "";
    779               });
    780   ProcessFlag("center_y", args->center_y,
    781               JXL_ENC_FRAME_SETTING_GROUP_ORDER_CENTER_Y, params,
    782               [](int64_t x) -> std::string {
    783                 if (x < -1) {
    784                   return "Valid values are: -1 or [0 .. ysize).";
    785                 }
    786                 return "";
    787               });
    788 
    789   // Progressive/responsive mode settings.
    790   bool responsive_set = cmdline->GetOption(args->opt_responsive_id)->matched();
    791 
    792   ProcessFlag("progressive_dc", args->progressive_dc,
    793               JXL_ENC_FRAME_SETTING_PROGRESSIVE_DC, params,
    794               [](int64_t x) -> std::string {
    795                 return (-1 <= x && x <= 2) ? ""
    796                                            : "Valid range is {-1, 0, 1, 2}.\n";
    797               });
    798   ProcessFlag("progressive_ac", static_cast<int64_t>(args->progressive_ac),
    799               JXL_ENC_FRAME_SETTING_PROGRESSIVE_AC, params);
    800 
    801   if (args->progressive) {
    802     args->qprogressive_ac = true;
    803     args->responsive = 1;
    804     responsive_set = true;
    805   }
    806   if (responsive_set) {
    807     ProcessFlag("responsive", args->responsive,
    808                 JXL_ENC_FRAME_SETTING_RESPONSIVE, params);
    809   }
    810   if (args->qprogressive_ac) {
    811     ProcessFlag("qprogressive_ac", static_cast<int64_t>(1),
    812                 JXL_ENC_FRAME_SETTING_QPROGRESSIVE_AC, params);
    813   }
    814 
    815   // Modular mode related.
    816   ProcessFlag("modular_group_size", args->modular_group_size,
    817               JXL_ENC_FRAME_SETTING_MODULAR_GROUP_SIZE, params,
    818               [](int64_t x) -> std::string {
    819                 return (-1 <= x && x <= 3)
    820                            ? ""
    821                            : "Invalid --modular_group_size. Valid "
    822                              "range is {-1, 0, 1, 2, 3}.\n";
    823               });
    824   ProcessFlag("modular_predictor", args->modular_predictor,
    825               JXL_ENC_FRAME_SETTING_MODULAR_PREDICTOR, params,
    826               [](int64_t x) -> std::string {
    827                 return (-1 <= x && x <= 15)
    828                            ? ""
    829                            : "Invalid --modular_predictor. Valid "
    830                              "range is {-1, 0, 1, ..., 15}.\n";
    831               });
    832   ProcessFlag("modular_colorspace", args->modular_colorspace,
    833               JXL_ENC_FRAME_SETTING_MODULAR_COLOR_SPACE, params,
    834               [](int64_t x) -> std::string {
    835                 return (-1 <= x && x <= 41)
    836                            ? ""
    837                            : "Invalid --modular_colorspace. Valid range is "
    838                              "{-1, 0, 1, ..., 41}.\n";
    839               });
    840   ProcessFlag("modular_ma_tree_learning_percent",
    841               args->modular_ma_tree_learning_percent,
    842               JXL_ENC_FRAME_SETTING_MODULAR_MA_TREE_LEARNING_PERCENT, params,
    843               [](float x) -> std::string {
    844                 return -1 <= x && x <= 100
    845                            ? ""
    846                            : "Invalid --modular_ma_tree_learning_percent, Valid"
    847                              "rang is [-1, 100].\n";
    848               });
    849   ProcessFlag("modular_nb_prev_channels", args->modular_nb_prev_channels,
    850               JXL_ENC_FRAME_SETTING_MODULAR_NB_PREV_CHANNELS, params,
    851               [](int64_t x) -> std::string {
    852                 return (-1 <= x && x <= 11)
    853                            ? ""
    854                            : "Invalid --modular_nb_prev_channels. Valid "
    855                              "range is {-1, 0, 1, ..., 11}.\n";
    856               });
    857   if (args->modular_lossy_palette) {
    858     if (args->progressive || args->qprogressive_ac) {
    859       fprintf(stderr,
    860               "WARNING: --modular_lossy_palette is ignored in "
    861               "progressive mode.\n");
    862       args->modular_lossy_palette = false;
    863     }
    864   }
    865   ProcessFlag("modular_lossy_palette",
    866               static_cast<int64_t>(args->modular_lossy_palette),
    867               JXL_ENC_FRAME_SETTING_LOSSY_PALETTE, params);
    868   ProcessFlag("modular_palette_colors", args->modular_palette_colors,
    869               JXL_ENC_FRAME_SETTING_PALETTE_COLORS, params,
    870               [](int64_t x) -> std::string {
    871                 return -1 <= x ? ""
    872                                : "Invalid --modular_palette_colors, must "
    873                                  "be -1 or non-negative\n";
    874               });
    875   ProcessFlag("modular_channel_colors_global_percent",
    876               args->modular_channel_colors_global_percent,
    877               JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GLOBAL_PERCENT, params,
    878               [](float x) -> std::string {
    879                 return (-1 <= x && x <= 100)
    880                            ? ""
    881                            : "Invalid --modular_channel_colors_global_percent. "
    882                              "Valid "
    883                              "range is [-1, 100].\n";
    884               });
    885   ProcessFlag("modular_channel_colors_group_percent",
    886               args->modular_channel_colors_group_percent,
    887               JXL_ENC_FRAME_SETTING_CHANNEL_COLORS_GROUP_PERCENT, params,
    888               [](float x) -> std::string {
    889                 return (-1 <= x && x <= 100)
    890                            ? ""
    891                            : "Invalid --modular_channel_colors_group_percent. "
    892                              "Valid "
    893                              "range is [-1, 100].\n";
    894               });
    895 
    896   if (args->num_threads < -1) {
    897     std::cerr
    898         << "Invalid flag value for --num_threads: must be -1, 0 or positive."
    899         << std::endl;
    900     exit(EXIT_FAILURE);
    901   }
    902   // JPEG specific options.
    903   if (jpeg_bytes) {
    904     ProcessBoolFlag(args->jpeg_reconstruction_cfl,
    905                     JXL_ENC_FRAME_SETTING_JPEG_RECON_CFL, params);
    906     ProcessBoolFlag(args->compress_boxes,
    907                     JXL_ENC_FRAME_SETTING_JPEG_COMPRESS_BOXES, params);
    908   }
    909   // Set per-frame options.
    910   for (size_t num_frame = 0; num_frame < ppf.num_frames(); ++num_frame) {
    911     if (num_frame < args->frame_indexing.size() &&
    912         args->frame_indexing[num_frame] == '1') {
    913       int64_t value = 1;
    914       params->options.emplace_back(JXL_ENC_FRAME_INDEX_BOX, value, num_frame);
    915     }
    916   }
    917   // Copy over the rest of the non-option params.
    918   params->use_container = args->container == jxl::Override::kOn;
    919   params->jpeg_store_metadata = FROM_JXL_BOOL(args->allow_jpeg_reconstruction);
    920   params->intensity_target = args->intensity_target;
    921   params->override_bitdepth = args->override_bitdepth;
    922   params->codestream_level = args->codestream_level;
    923   params->premultiply = args->premultiply;
    924   params->compress_boxes = args->compress_boxes != jxl::Override::kOff;
    925   params->upsampling_mode = args->upsampling_mode;
    926   if (codec == jxl::extras::Codec::kPNM &&
    927       ppf.info.exponent_bits_per_sample == 0) {
    928     params->input_bitdepth.type = JXL_BIT_DEPTH_FROM_CODESTREAM;
    929   }
    930 
    931   // If a metadata field is set to an empty value, it is stripped.
    932   // Make sure we also strip it when the input image is read with AddJPEGFrame
    933   (void)args->color_hints_proxy.target.Foreach(
    934       [&params](const std::string& key,
    935                 const std::string& value) -> jxl::Status {
    936         if (value.empty()) {
    937           if (key == "exif") params->jpeg_strip_exif = true;
    938           if (key == "xmp") params->jpeg_strip_xmp = true;
    939           if (key == "jumbf") params->jpeg_strip_jumbf = true;
    940         }
    941         return true;
    942       });
    943 }
    944 
    945 struct JxlOutputProcessor {
    946   bool SetOutputPath(const std::string& path) {
    947     outfile.reset(new FileWrapper(path, "wb"));
    948     if (!*outfile) {
    949       fprintf(stderr,
    950               "Could not open %s for writing\n"
    951               "Error: %s",
    952               path.c_str(), strerror(errno));
    953       return false;
    954     }
    955     return true;
    956   }
    957 
    958   JxlEncoderOutputProcessor GetOutputProcessor() {
    959     return JxlEncoderOutputProcessor{
    960         this, METHOD_TO_C_CALLBACK(&JxlOutputProcessor::GetBuffer),
    961         METHOD_TO_C_CALLBACK(&JxlOutputProcessor::ReleaseBuffer),
    962         METHOD_TO_C_CALLBACK(&JxlOutputProcessor::Seek),
    963         METHOD_TO_C_CALLBACK(&JxlOutputProcessor::SetFinalizedPosition)};
    964   }
    965 
    966   void* GetBuffer(size_t* size) {
    967     *size = std::min<size_t>(*size, 1u << 16);
    968     if (output.size() < *size) {
    969       output.resize(*size);
    970     }
    971     return output.data();
    972   }
    973 
    974   void ReleaseBuffer(size_t written_bytes) {
    975     if (*outfile &&
    976         fwrite(output.data(), 1, written_bytes, *outfile) != written_bytes) {
    977       JXL_WARNING("Failed to write %" PRIuS " bytes to output", written_bytes);
    978     }
    979     output.clear();
    980   }
    981 
    982   void Seek(uint64_t position) {
    983     if (*outfile && fseek(*outfile, position, SEEK_SET) != 0) {
    984       JXL_WARNING("Failed to seek output.");
    985     }
    986   }
    987 
    988   void SetFinalizedPosition(uint64_t finalized_position) {
    989     this->finalized_position = finalized_position;
    990   }
    991 
    992   std::vector<uint8_t> output;
    993   size_t finalized_position = 0;
    994   std::unique_ptr<FileWrapper> outfile;
    995 };
    996 
    997 }  // namespace tools
    998 }  // namespace jpegxl
    999 
   1000 int main(int argc, char** argv) {
   1001   std::string version = jpegxl::tools::CodecConfigString(JxlEncoderVersion());
   1002   jpegxl::tools::CompressArgs args;
   1003   jpegxl::tools::CommandLineParser cmdline;
   1004   args.AddCommandLineOptions(&cmdline);
   1005 
   1006   if (!cmdline.Parse(argc, const_cast<const char**>(argv))) {
   1007     // Parse already printed the actual error cause.
   1008     fprintf(stderr, "Use '%s -h' for more information\n", argv[0]);
   1009     return jpegxl::tools::CjxlRetCode::ERR_PARSE;
   1010   }
   1011 
   1012   if (args.version) {
   1013     fprintf(stdout, "cjxl %s\n", version.c_str());
   1014     fprintf(stdout, "Copyright (c) the JPEG XL Project\n");
   1015     return jpegxl::tools::CjxlRetCode::OK;
   1016   }
   1017 
   1018   if (!args.quiet) {
   1019     fprintf(stderr, "JPEG XL encoder %s\n", version.c_str());
   1020   }
   1021 
   1022   if (cmdline.HelpFlagPassed() || !args.file_in) {
   1023     cmdline.PrintHelp();
   1024     return jpegxl::tools::CjxlRetCode::OK;
   1025   }
   1026 
   1027   if (!args.file_out && !args.disable_output) {
   1028     std::cerr
   1029         << "No output file specified and --disable_output flag not passed."
   1030         << std::endl;
   1031     exit(EXIT_FAILURE);
   1032   }
   1033 
   1034   if (args.file_out && args.disable_output && !args.quiet) {
   1035     fprintf(stderr,
   1036             "Encoding will be performed, but the result will be discarded.\n");
   1037   }
   1038 
   1039   jxl::extras::JXLCompressParams params;
   1040   jxl::extras::PackedPixelFile ppf;
   1041   jxl::extras::Codec codec = jxl::extras::Codec::kUnknown;
   1042   std::vector<uint8_t> image_data;
   1043   std::vector<uint8_t>* jpeg_bytes = nullptr;
   1044   size_t input_bytes = 0;
   1045   double decode_mps = 0;
   1046   size_t pixels = 0;
   1047   jxl::extras::ChunkedPNMDecoder pnm_dec;
   1048   if (args.streaming_input) {
   1049     auto dec = jxl::extras::ChunkedPNMDecoder::Init(args.file_in);
   1050     if (!dec.ok()) {
   1051       std::cerr << "PNM decoding failed." << std::endl;
   1052       exit(EXIT_FAILURE);
   1053     }
   1054     pnm_dec = std::move(dec).value();
   1055     JXL_RETURN_IF_ERROR(
   1056         pnm_dec.InitializePPF(args.color_hints_proxy.target, &ppf));
   1057     codec = jxl::extras::Codec::kPNM;
   1058     args.lossless_jpeg = JXL_FALSE;
   1059     pixels = ppf.info.xsize * ppf.info.ysize;
   1060   } else {
   1061     // Loading the input.
   1062     // Depending on flags-settings, we want to either load a JPEG and
   1063     // faithfully convert it to JPEG XL, or load (JPEG or non-JPEG)
   1064     // pixel data.
   1065     jpegxl::tools::FileWrapper f(args.file_in, "rb");
   1066     if (!f) {
   1067       std::cerr << "Reading image data failed." << std::endl;
   1068       exit(EXIT_FAILURE);
   1069     }
   1070     if (!jpegxl::tools::ReadFile(f, &image_data)) {
   1071       std::cerr << "Reading image data failed." << std::endl;
   1072       exit(EXIT_FAILURE);
   1073     }
   1074     input_bytes = image_data.size();
   1075     if (!jpegxl::tools::IsJPG(image_data)) args.lossless_jpeg = JXL_FALSE;
   1076     ProcessFlags(codec, ppf, jpeg_bytes, &cmdline, &args, &params);
   1077     if (!FROM_JXL_BOOL(args.lossless_jpeg)) {
   1078       const double t0 = jxl::Now();
   1079       jxl::Status status = jxl::extras::DecodeBytes(
   1080           jxl::Bytes(image_data), args.color_hints_proxy.target, &ppf, nullptr,
   1081           &codec);
   1082 
   1083       if (!status) {
   1084         std::cerr << "Getting pixel data failed." << std::endl;
   1085         exit(EXIT_FAILURE);
   1086       }
   1087       if (ppf.frames.empty()) {
   1088         std::cerr << "No frames on input file." << std::endl;
   1089         exit(EXIT_FAILURE);
   1090       }
   1091       pixels = ppf.info.xsize * ppf.info.ysize;
   1092       const double t1 = jxl::Now();
   1093       decode_mps = pixels * ppf.info.num_color_channels * 1E-6 / (t1 - t0);
   1094     }
   1095 
   1096     if (FROM_JXL_BOOL(args.lossless_jpeg) && jpegxl::tools::IsJPG(image_data)) {
   1097       if (!cmdline.GetOption(args.opt_lossless_jpeg_id)->matched()) {
   1098         std::cerr << "Note: Implicit-default for JPEG is lossless-transcoding. "
   1099                   << "To silence this message, set --lossless_jpeg=(1|0)."
   1100                   << std::endl;
   1101       }
   1102       jpeg_bytes = &image_data;
   1103       if (args.allow_jpeg_reconstruction) {
   1104         (void)args.color_hints_proxy.target.Foreach([](const std::string& key,
   1105                                                        const std::string& value)
   1106                                                         -> jxl::Status {
   1107           if (value.empty()) {
   1108             if (key != "jumbf") {
   1109               std::cerr
   1110                   << "Cannot strip " << key
   1111                   << " metadata, try setting --allow_jpeg_reconstruction=0. "
   1112                      "Note that with that setting byte exact reconstruction "
   1113                      "of the JPEG file won't be possible."
   1114                   << std::endl;
   1115               exit(EXIT_FAILURE);
   1116             }
   1117           }
   1118           return true;
   1119         });
   1120       }
   1121     }
   1122   }
   1123 
   1124   ProcessFlags(codec, ppf, jpeg_bytes, &cmdline, &args, &params);
   1125 
   1126   if (!args.quiet) {
   1127     PrintMode(ppf, decode_mps, input_bytes, args, cmdline);
   1128   }
   1129 
   1130   if (!ppf.metadata.exif.empty()) {
   1131     jxl::InterpretExif(ppf.metadata.exif, &ppf.info.orientation);
   1132   }
   1133 
   1134   if (!ppf.metadata.exif.empty() || !ppf.metadata.xmp.empty() ||
   1135       !ppf.metadata.jumbf.empty() || !ppf.metadata.iptc.empty() ||
   1136       (FROM_JXL_BOOL(args.lossless_jpeg) &&
   1137        FROM_JXL_BOOL(args.allow_jpeg_reconstruction))) {
   1138     if (args.container == jxl::Override::kDefault) {
   1139       args.container = jxl::Override::kOn;
   1140     } else if (args.container == jxl::Override::kOff) {
   1141       cmdline.VerbosePrintf(
   1142           1, "Stripping all metadata due to explicit container=0\n");
   1143       ppf.metadata.exif.clear();
   1144       ppf.metadata.xmp.clear();
   1145       ppf.metadata.jumbf.clear();
   1146       ppf.metadata.iptc.clear();
   1147       args.allow_jpeg_reconstruction = JXL_FALSE;
   1148     }
   1149   }
   1150 
   1151   size_t num_worker_threads = JxlThreadParallelRunnerDefaultNumWorkerThreads();
   1152   int64_t flag_num_worker_threads = args.num_threads;
   1153   if (flag_num_worker_threads > -1) {
   1154     num_worker_threads = flag_num_worker_threads;
   1155   }
   1156   JxlThreadParallelRunnerPtr runner = JxlThreadParallelRunnerMake(
   1157       /*memory_manager=*/nullptr, num_worker_threads);
   1158   params.runner = JxlThreadParallelRunner;
   1159   params.runner_opaque = runner.get();
   1160 
   1161   if (args.streaming_input) {
   1162     params.options.emplace_back(JXL_ENC_FRAME_SETTING_BUFFERING,
   1163                                 static_cast<int64_t>(3), 0);
   1164   }
   1165 
   1166   jpegxl::tools::SpeedStats stats;
   1167   jpegxl::tools::JxlOutputProcessor output_processor;
   1168   bool have_file_out = (args.file_out != nullptr);
   1169   if (args.streaming_output) {
   1170     if (have_file_out && !args.disable_output &&
   1171         !output_processor.SetOutputPath(args.file_out)) {
   1172       return EXIT_FAILURE;
   1173     }
   1174     params.output_processor = output_processor.GetOutputProcessor();
   1175   }
   1176   std::vector<uint8_t> compressed;
   1177   for (size_t num_rep = 0; num_rep < args.num_reps; ++num_rep) {
   1178     if (args.streaming_output) {
   1179       output_processor.Seek(0);
   1180       output_processor.SetFinalizedPosition(0);
   1181     }
   1182     const double t0 = jxl::Now();
   1183     if (!EncodeImageJXL(params, ppf, jpeg_bytes,
   1184                         args.streaming_output ? nullptr : &compressed)) {
   1185       fprintf(stderr, "EncodeImageJXL() failed.\n");
   1186       return EXIT_FAILURE;
   1187     }
   1188     const double t1 = jxl::Now();
   1189     stats.NotifyElapsed(t1 - t0);
   1190     stats.SetImageSize(ppf.info.xsize, ppf.info.ysize);
   1191   }
   1192   size_t compressed_size = args.streaming_output
   1193                                ? output_processor.finalized_position
   1194                                : compressed.size();
   1195 
   1196   if (!args.streaming_output && have_file_out && !args.disable_output) {
   1197     if (!jpegxl::tools::WriteFile(args.file_out, compressed)) {
   1198       std::cerr << "Could not write jxl file." << std::endl;
   1199       return EXIT_FAILURE;
   1200     }
   1201   }
   1202   if (!args.quiet) {
   1203     if (compressed_size < 100000) {
   1204       cmdline.VerbosePrintf(0, "Compressed to %" PRIuS " bytes ",
   1205                             compressed_size);
   1206     } else {
   1207       cmdline.VerbosePrintf(0, "Compressed to %.1f kB ",
   1208                             compressed_size * 0.001);
   1209     }
   1210     // For lossless jpeg-reconstruction, we don't print some stats, since we
   1211     // don't have easy access to the image dimensions.
   1212     if (args.container == jxl::Override::kOn) {
   1213       cmdline.VerbosePrintf(0, "including container ");
   1214     }
   1215     if (!FROM_JXL_BOOL(args.lossless_jpeg)) {
   1216       const double bpp =
   1217           static_cast<double>(compressed_size * jxl::kBitsPerByte) / pixels;
   1218       cmdline.VerbosePrintf(0, "(%.3f bpp%s).\n", bpp / ppf.num_frames(),
   1219                             ppf.num_frames() == 1 ? "" : "/frame");
   1220       JXL_CHECK(stats.Print(num_worker_threads));
   1221     } else {
   1222       cmdline.VerbosePrintf(0, "\n");
   1223     }
   1224   }
   1225   return EXIT_SUCCESS;
   1226 }