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 }