summaryrefslogtreecommitdiff
path: root/tools/cjpegli.cc
diff options
context:
space:
mode:
Diffstat (limited to 'tools/cjpegli.cc')
-rw-r--r--tools/cjpegli.cc270
1 files changed, 270 insertions, 0 deletions
diff --git a/tools/cjpegli.cc b/tools/cjpegli.cc
new file mode 100644
index 0000000..4088e27
--- /dev/null
+++ b/tools/cjpegli.cc
@@ -0,0 +1,270 @@
+// Copyright (c) the JPEG XL Project Authors. All rights reserved.
+//
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <vector>
+
+#include "lib/extras/dec/decode.h"
+#include "lib/extras/enc/jpegli.h"
+#include "lib/extras/time.h"
+#include "lib/jxl/base/printf_macros.h"
+#include "lib/jxl/base/span.h"
+#include "tools/args.h"
+#include "tools/cmdline.h"
+#include "tools/file_io.h"
+#include "tools/speed_stats.h"
+
+namespace jpegxl {
+namespace tools {
+namespace {
+
+struct Args {
+ void AddCommandLineOptions(CommandLineParser* cmdline) {
+ std::string input_help("the input can be ");
+ if (jxl::extras::CanDecode(jxl::extras::Codec::kPNG)) {
+ input_help.append("PNG, APNG, ");
+ }
+ if (jxl::extras::CanDecode(jxl::extras::Codec::kGIF)) {
+ input_help.append("GIF, ");
+ }
+ if (jxl::extras::CanDecode(jxl::extras::Codec::kEXR)) {
+ input_help.append("EXR, ");
+ }
+ input_help.append("PPM, PFM, or PGX");
+ cmdline->AddPositionalOption("INPUT", /* required = */ true, input_help,
+ &file_in);
+ cmdline->AddPositionalOption("OUTPUT", /* required = */ true,
+ "the compressed JPG output file", &file_out);
+
+ cmdline->AddOptionFlag('\0', "disable_output",
+ "No output file will be written (for benchmarking)",
+ &disable_output, &SetBooleanTrue, 1);
+
+ cmdline->AddOptionValue(
+ 'x', "dec-hints", "key=value",
+ "color_space indicates the ColorEncoding, see Description();\n"
+ " icc_pathname refers to a binary file containing an ICC profile.",
+ &color_hints_proxy, &ParseAndAppendKeyValue<ColorHintsProxy>, 1);
+
+ opt_distance_id = cmdline->AddOptionValue(
+ 'd', "distance", "maxError",
+ "Max. butteraugli distance, lower = higher quality.\n"
+ " 1.0 = visually lossless (default).\n"
+ " Recommended range: 0.5 .. 3.0. Allowed range: 0.0 ... 25.0.\n"
+ " Mutually exclusive with --quality and --target_size.",
+ &settings.distance, &ParseFloat);
+
+ opt_quality_id = cmdline->AddOptionValue(
+ 'q', "quality", "QUALITY",
+ "Quality setting (is remapped to --distance)."
+ " Default is quality 90.\n"
+ " Quality values roughly match libjpeg quality.\n"
+ " Recommended range: 68 .. 96. Allowed range: 1 .. 100.\n"
+ " Mutually exclusive with --distance and --target_size.",
+ &quality, &ParseSigned);
+
+ cmdline->AddOptionValue('\0', "chroma_subsampling", "444|440|422|420",
+ "Chroma subsampling setting.",
+ &settings.chroma_subsampling, &ParseString);
+
+ cmdline->AddOptionValue(
+ 'p', "progressive_level", "N",
+ "Progressive level setting. Range: 0 .. 2.\n"
+ " Default: 2. Higher number is more scans, 0 means sequential.",
+ &settings.progressive_level, &ParseSigned);
+
+ cmdline->AddOptionFlag('\0', "xyb", "Convert to XYB colorspace",
+ &settings.xyb, &SetBooleanTrue, 1);
+
+ cmdline->AddOptionFlag(
+ '\0', "std_quant",
+ "Use quantization tables based on Annex K of the JPEG standard.",
+ &settings.use_std_quant_tables, &SetBooleanTrue, 1);
+
+ cmdline->AddOptionFlag(
+ '\0', "noadaptive_quantization", "Disable adaptive quantization.",
+ &settings.use_adaptive_quantization, &SetBooleanFalse, 1);
+
+ cmdline->AddOptionFlag(
+ '\0', "fixed_code",
+ "Disable Huffman code optimization. Must be used together with -p 0.",
+ &settings.optimize_coding, &SetBooleanFalse, 1);
+
+ cmdline->AddOptionValue(
+ '\0', "target_size", "N",
+ "If non-zero, set target size in bytes. This is useful for image \n"
+ " quality comparisons, but makes encoding speed up to 20x slower.\n"
+ " Mutually exclusive with --distance and --quality.",
+ &settings.target_size, &ParseUnsigned, 2);
+
+ cmdline->AddOptionValue('\0', "num_reps", "N",
+ "How many times to compress. (For benchmarking).",
+ &num_reps, &ParseUnsigned, 1);
+
+ cmdline->AddOptionFlag('\0', "quiet", "Suppress informative output", &quiet,
+ &SetBooleanTrue, 1);
+
+ cmdline->AddOptionFlag(
+ 'v', "verbose",
+ "Verbose output; can be repeated, also applies to help (!).", &verbose,
+ &SetBooleanTrue);
+ }
+
+ const char* file_in = nullptr;
+ const char* file_out = nullptr;
+ bool disable_output = false;
+ ColorHintsProxy color_hints_proxy;
+ jxl::extras::JpegSettings settings;
+ int quality = 90;
+ size_t num_reps = 1;
+ bool quiet = false;
+ bool verbose = false;
+ // References (ids) of specific options to check if they were matched.
+ CommandLineParser::OptionId opt_distance_id = -1;
+ CommandLineParser::OptionId opt_quality_id = -1;
+};
+
+bool ValidateArgs(const Args& args) {
+ const jxl::extras::JpegSettings& settings = args.settings;
+ if (settings.distance < 0.0 || settings.distance > 25.0) {
+ fprintf(stderr, "Invalid --distance argument\n");
+ return false;
+ }
+ if (args.quality <= 0 || args.quality > 100) {
+ fprintf(stderr, "Invalid --quality argument\n");
+ return false;
+ }
+ std::string cs = settings.chroma_subsampling;
+ if (!cs.empty() && cs != "444" && cs != "440" && cs != "422" && cs != "420") {
+ fprintf(stderr, "Invalid --chroma_subsampling argument\n");
+ return false;
+ }
+ if (settings.progressive_level < 0 || settings.progressive_level > 2) {
+ fprintf(stderr, "Invalid --progressive_level argument\n");
+ return false;
+ }
+ if (settings.progressive_level > 0 && !settings.optimize_coding) {
+ fprintf(stderr, "--fixed_code must be used together with -p 0\n");
+ return false;
+ }
+ return true;
+}
+
+bool SetDistance(const Args& args, const CommandLineParser& cmdline,
+ jxl::extras::JpegSettings* settings) {
+ bool distance_set = cmdline.GetOption(args.opt_distance_id)->matched();
+ bool quality_set = cmdline.GetOption(args.opt_quality_id)->matched();
+ int num_quality_settings = (distance_set ? 1 : 0) + (quality_set ? 1 : 0) +
+ (args.settings.target_size > 0 ? 1 : 0);
+ if (num_quality_settings > 1) {
+ fprintf(
+ stderr,
+ "Only one of --distance, --quality, or --target_size can be set.\n");
+ return false;
+ }
+ if (quality_set) {
+ settings->quality = args.quality;
+ }
+ return true;
+}
+
+int CJpegliMain(int argc, const char* argv[]) {
+ Args args;
+ CommandLineParser cmdline;
+ args.AddCommandLineOptions(&cmdline);
+
+ if (!cmdline.Parse(argc, const_cast<const char**>(argv))) {
+ // Parse already printed the actual error cause.
+ fprintf(stderr, "Use '%s -h' for more information.\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ if (cmdline.HelpFlagPassed() || !args.file_in) {
+ cmdline.PrintHelp();
+ return EXIT_SUCCESS;
+ }
+
+ if (!args.file_out && !args.disable_output) {
+ fprintf(stderr,
+ "No output file specified and --disable_output flag not passed.\n");
+ return EXIT_FAILURE;
+ }
+
+ if (args.disable_output && !args.quiet) {
+ fprintf(stderr,
+ "Encoding will be performed, but the result will be discarded.\n");
+ }
+
+ std::vector<uint8_t> input_bytes;
+ if (!ReadFile(args.file_in, &input_bytes)) {
+ fprintf(stderr, "Failed to read input image %s\n", args.file_in);
+ return EXIT_FAILURE;
+ }
+
+ jxl::extras::PackedPixelFile ppf;
+ if (!jxl::extras::DecodeBytes(jxl::Bytes(input_bytes),
+ args.color_hints_proxy.target, &ppf)) {
+ fprintf(stderr, "Failed to decode input image %s\n", args.file_in);
+ return EXIT_FAILURE;
+ }
+
+ if (!args.quiet) {
+ fprintf(stderr, "Read %ux%u image, %" PRIuS " bytes.\n", ppf.info.xsize,
+ ppf.info.ysize, input_bytes.size());
+ }
+
+ if (!ValidateArgs(args) || !SetDistance(args, cmdline, &args.settings)) {
+ return EXIT_FAILURE;
+ }
+
+ if (!args.quiet) {
+ const jxl::extras::JpegSettings& s = args.settings;
+ fprintf(stderr, "Encoding [%s%s d%.3f%s %sAQ p%d %s]\n",
+ s.xyb ? "XYB" : "YUV", s.chroma_subsampling.c_str(), s.distance,
+ s.use_std_quant_tables ? " StdQuant" : "",
+ s.use_adaptive_quantization ? "" : "no", s.progressive_level,
+ s.optimize_coding ? "OPT" : "FIX");
+ }
+
+ jpegxl::tools::SpeedStats stats;
+ std::vector<uint8_t> jpeg_bytes;
+ for (size_t num_rep = 0; num_rep < args.num_reps; ++num_rep) {
+ const double t0 = jxl::Now();
+ if (!jxl::extras::EncodeJpeg(ppf, args.settings, nullptr, &jpeg_bytes)) {
+ fprintf(stderr, "jpegli encoding failed\n");
+ return EXIT_FAILURE;
+ }
+ const double t1 = jxl::Now();
+ stats.NotifyElapsed(t1 - t0);
+ stats.SetImageSize(ppf.info.xsize, ppf.info.ysize);
+ }
+
+ if (args.file_out && !args.disable_output) {
+ if (!WriteFile(args.file_out, jpeg_bytes)) {
+ fprintf(stderr, "Could not write jpeg to %s\n", args.file_out);
+ return EXIT_FAILURE;
+ }
+ }
+ if (!args.quiet) {
+ fprintf(stderr, "Compressed to %" PRIuS " bytes ", jpeg_bytes.size());
+ const size_t num_pixels = ppf.info.xsize * ppf.info.ysize;
+ const double bpp =
+ static_cast<double>(jpeg_bytes.size() * jxl::kBitsPerByte) / num_pixels;
+ fprintf(stderr, "(%.3f bpp).\n", bpp);
+ stats.Print(1);
+ }
+ return EXIT_SUCCESS;
+}
+
+} // namespace
+} // namespace tools
+} // namespace jpegxl
+
+int main(int argc, const char** argv) {
+ return jpegxl::tools::CJpegliMain(argc, argv);
+}