libjxl

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

cjpegli.cc (9650B)


      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 <stdint.h>
      7 #include <stdio.h>
      8 #include <stdlib.h>
      9 
     10 #include <vector>
     11 
     12 #include "lib/extras/dec/decode.h"
     13 #include "lib/extras/enc/jpegli.h"
     14 #include "lib/extras/time.h"
     15 #include "lib/jxl/base/printf_macros.h"
     16 #include "lib/jxl/base/span.h"
     17 #include "tools/args.h"
     18 #include "tools/cmdline.h"
     19 #include "tools/file_io.h"
     20 #include "tools/speed_stats.h"
     21 
     22 namespace jpegxl {
     23 namespace tools {
     24 namespace {
     25 
     26 struct Args {
     27   void AddCommandLineOptions(CommandLineParser* cmdline) {
     28     std::string input_help("the input can be ");
     29     if (jxl::extras::CanDecode(jxl::extras::Codec::kPNG)) {
     30       input_help.append("PNG, APNG, ");
     31     }
     32     if (jxl::extras::CanDecode(jxl::extras::Codec::kGIF)) {
     33       input_help.append("GIF, ");
     34     }
     35     if (jxl::extras::CanDecode(jxl::extras::Codec::kEXR)) {
     36       input_help.append("EXR, ");
     37     }
     38     input_help.append("PPM, PFM, or PGX");
     39     cmdline->AddPositionalOption("INPUT", /* required = */ true, input_help,
     40                                  &file_in);
     41     cmdline->AddPositionalOption("OUTPUT", /* required = */ true,
     42                                  "the compressed JPG output file", &file_out);
     43 
     44     cmdline->AddOptionFlag('\0', "disable_output",
     45                            "No output file will be written (for benchmarking)",
     46                            &disable_output, &SetBooleanTrue, 1);
     47 
     48     cmdline->AddOptionValue(
     49         'x', "dec-hints", "key=value",
     50         "color_space indicates the ColorEncoding, see Description();\n"
     51         "    icc_pathname refers to a binary file containing an ICC profile.",
     52         &color_hints_proxy, &ParseAndAppendKeyValue<ColorHintsProxy>, 1);
     53 
     54     opt_distance_id = cmdline->AddOptionValue(
     55         'd', "distance", "maxError",
     56         "Max. butteraugli distance, lower = higher quality.\n"
     57         "    1.0 = visually lossless (default).\n"
     58         "    Recommended range: 0.5 .. 3.0. Allowed range: 0.0 ... 25.0.\n"
     59         "    Mutually exclusive with --quality and --target_size.",
     60         &settings.distance, &ParseFloat);
     61 
     62     opt_quality_id = cmdline->AddOptionValue(
     63         'q', "quality", "QUALITY",
     64         "Quality setting (is remapped to --distance)."
     65         "    Default is quality 90.\n"
     66         "    Quality values roughly match libjpeg quality.\n"
     67         "    Recommended range: 68 .. 96. Allowed range: 1 .. 100.\n"
     68         "    Mutually exclusive with --distance and --target_size.",
     69         &quality, &ParseSigned);
     70 
     71     cmdline->AddOptionValue('\0', "chroma_subsampling", "444|440|422|420",
     72                             "Chroma subsampling setting.",
     73                             &settings.chroma_subsampling, &ParseString);
     74 
     75     cmdline->AddOptionValue(
     76         'p', "progressive_level", "N",
     77         "Progressive level setting. Range: 0 .. 2.\n"
     78         "    Default: 2. Higher number is more scans, 0 means sequential.",
     79         &settings.progressive_level, &ParseSigned);
     80 
     81     cmdline->AddOptionFlag('\0', "xyb", "Convert to XYB colorspace",
     82                            &settings.xyb, &SetBooleanTrue, 1);
     83 
     84     cmdline->AddOptionFlag(
     85         '\0', "std_quant",
     86         "Use quantization tables based on Annex K of the JPEG standard.",
     87         &settings.use_std_quant_tables, &SetBooleanTrue, 1);
     88 
     89     cmdline->AddOptionFlag(
     90         '\0', "noadaptive_quantization", "Disable adaptive quantization.",
     91         &settings.use_adaptive_quantization, &SetBooleanFalse, 1);
     92 
     93     cmdline->AddOptionFlag(
     94         '\0', "fixed_code",
     95         "Disable Huffman code optimization. Must be used together with -p 0.",
     96         &settings.optimize_coding, &SetBooleanFalse, 1);
     97 
     98     cmdline->AddOptionValue(
     99         '\0', "target_size", "N",
    100         "If non-zero, set target size in bytes. This is useful for image \n"
    101         "    quality comparisons, but makes encoding speed up to 20x slower.\n"
    102         "    Mutually exclusive with --distance and --quality.",
    103         &settings.target_size, &ParseUnsigned, 2);
    104 
    105     cmdline->AddOptionValue('\0', "num_reps", "N",
    106                             "How many times to compress. (For benchmarking).",
    107                             &num_reps, &ParseUnsigned, 1);
    108 
    109     cmdline->AddOptionFlag('\0', "quiet", "Suppress informative output", &quiet,
    110                            &SetBooleanTrue, 1);
    111 
    112     cmdline->AddOptionFlag(
    113         'v', "verbose",
    114         "Verbose output; can be repeated, also applies to help (!).", &verbose,
    115         &SetBooleanTrue);
    116   }
    117 
    118   const char* file_in = nullptr;
    119   const char* file_out = nullptr;
    120   bool disable_output = false;
    121   ColorHintsProxy color_hints_proxy;
    122   jxl::extras::JpegSettings settings;
    123   int quality = 90;
    124   size_t num_reps = 1;
    125   bool quiet = false;
    126   bool verbose = false;
    127   // References (ids) of specific options to check if they were matched.
    128   CommandLineParser::OptionId opt_distance_id = -1;
    129   CommandLineParser::OptionId opt_quality_id = -1;
    130 };
    131 
    132 bool ValidateArgs(const Args& args) {
    133   const jxl::extras::JpegSettings& settings = args.settings;
    134   if (settings.distance < 0.0 || settings.distance > 25.0) {
    135     fprintf(stderr, "Invalid --distance argument\n");
    136     return false;
    137   }
    138   if (args.quality <= 0 || args.quality > 100) {
    139     fprintf(stderr, "Invalid --quality argument\n");
    140     return false;
    141   }
    142   std::string cs = settings.chroma_subsampling;
    143   if (!cs.empty() && cs != "444" && cs != "440" && cs != "422" && cs != "420") {
    144     fprintf(stderr, "Invalid --chroma_subsampling argument\n");
    145     return false;
    146   }
    147   if (settings.progressive_level < 0 || settings.progressive_level > 2) {
    148     fprintf(stderr, "Invalid --progressive_level argument\n");
    149     return false;
    150   }
    151   if (settings.progressive_level > 0 && !settings.optimize_coding) {
    152     fprintf(stderr, "--fixed_code must be used together with -p 0\n");
    153     return false;
    154   }
    155   return true;
    156 }
    157 
    158 bool SetDistance(const Args& args, const CommandLineParser& cmdline,
    159                  jxl::extras::JpegSettings* settings) {
    160   bool distance_set = cmdline.GetOption(args.opt_distance_id)->matched();
    161   bool quality_set = cmdline.GetOption(args.opt_quality_id)->matched();
    162   int num_quality_settings = (distance_set ? 1 : 0) + (quality_set ? 1 : 0) +
    163                              (args.settings.target_size > 0 ? 1 : 0);
    164   if (num_quality_settings > 1) {
    165     fprintf(
    166         stderr,
    167         "Only one of --distance, --quality, or --target_size can be set.\n");
    168     return false;
    169   }
    170   if (quality_set) {
    171     settings->quality = args.quality;
    172   }
    173   return true;
    174 }
    175 
    176 int CJpegliMain(int argc, const char* argv[]) {
    177   Args args;
    178   CommandLineParser cmdline;
    179   args.AddCommandLineOptions(&cmdline);
    180 
    181   if (!cmdline.Parse(argc, const_cast<const char**>(argv))) {
    182     // Parse already printed the actual error cause.
    183     fprintf(stderr, "Use '%s -h' for more information.\n", argv[0]);
    184     return EXIT_FAILURE;
    185   }
    186 
    187   if (cmdline.HelpFlagPassed() || !args.file_in) {
    188     cmdline.PrintHelp();
    189     return EXIT_SUCCESS;
    190   }
    191 
    192   if (!args.file_out && !args.disable_output) {
    193     fprintf(stderr,
    194             "No output file specified and --disable_output flag not passed.\n");
    195     return EXIT_FAILURE;
    196   }
    197 
    198   if (args.disable_output && !args.quiet) {
    199     fprintf(stderr,
    200             "Encoding will be performed, but the result will be discarded.\n");
    201   }
    202 
    203   std::vector<uint8_t> input_bytes;
    204   if (!ReadFile(args.file_in, &input_bytes)) {
    205     fprintf(stderr, "Failed to read input image %s\n", args.file_in);
    206     return EXIT_FAILURE;
    207   }
    208 
    209   jxl::extras::PackedPixelFile ppf;
    210   if (!jxl::extras::DecodeBytes(jxl::Bytes(input_bytes),
    211                                 args.color_hints_proxy.target, &ppf)) {
    212     fprintf(stderr, "Failed to decode input image %s\n", args.file_in);
    213     return EXIT_FAILURE;
    214   }
    215 
    216   if (!args.quiet) {
    217     fprintf(stderr, "Read %ux%u image, %" PRIuS " bytes.\n", ppf.info.xsize,
    218             ppf.info.ysize, input_bytes.size());
    219   }
    220 
    221   if (!ValidateArgs(args) || !SetDistance(args, cmdline, &args.settings)) {
    222     return EXIT_FAILURE;
    223   }
    224 
    225   if (!args.quiet) {
    226     const jxl::extras::JpegSettings& s = args.settings;
    227     fprintf(stderr, "Encoding [%s%s d%.3f%s %sAQ p%d %s]\n",
    228             s.xyb ? "XYB" : "YUV", s.chroma_subsampling.c_str(), s.distance,
    229             s.use_std_quant_tables ? " StdQuant" : "",
    230             s.use_adaptive_quantization ? "" : "no", s.progressive_level,
    231             s.optimize_coding ? "OPT" : "FIX");
    232   }
    233 
    234   jpegxl::tools::SpeedStats stats;
    235   std::vector<uint8_t> jpeg_bytes;
    236   for (size_t num_rep = 0; num_rep < args.num_reps; ++num_rep) {
    237     const double t0 = jxl::Now();
    238     if (!jxl::extras::EncodeJpeg(ppf, args.settings, nullptr, &jpeg_bytes)) {
    239       fprintf(stderr, "jpegli encoding failed\n");
    240       return EXIT_FAILURE;
    241     }
    242     const double t1 = jxl::Now();
    243     stats.NotifyElapsed(t1 - t0);
    244     stats.SetImageSize(ppf.info.xsize, ppf.info.ysize);
    245   }
    246 
    247   if (args.file_out && !args.disable_output) {
    248     if (!WriteFile(args.file_out, jpeg_bytes)) {
    249       fprintf(stderr, "Could not write jpeg to %s\n", args.file_out);
    250       return EXIT_FAILURE;
    251     }
    252   }
    253   if (!args.quiet) {
    254     fprintf(stderr, "Compressed to %" PRIuS " bytes ", jpeg_bytes.size());
    255     const size_t num_pixels = ppf.info.xsize * ppf.info.ysize;
    256     const double bpp =
    257         static_cast<double>(jpeg_bytes.size() * jxl::kBitsPerByte) / num_pixels;
    258     fprintf(stderr, "(%.3f bpp).\n", bpp);
    259     stats.Print(1);
    260   }
    261   return EXIT_SUCCESS;
    262 }
    263 
    264 }  // namespace
    265 }  // namespace tools
    266 }  // namespace jpegxl
    267 
    268 int main(int argc, const char** argv) {
    269   return jpegxl::tools::CJpegliMain(argc, argv);
    270 }