singe/thirdparty/SDL2_image/external/libjxl/tools/cjxl.cc
2023-10-23 19:38:18 -05:00

851 lines
32 KiB
C++

// 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 "tools/cjxl.h"
#include <math.h>
#include <stdint.h>
#include <stdio.h>
#include <algorithm>
#include <string>
#include <utility>
#include <vector>
#include "lib/extras/codec.h"
#if JPEGXL_ENABLE_JPEG
#include "lib/extras/codec_jpg.h"
#endif
#include "lib/extras/time.h"
#include "lib/jxl/aux_out.h"
#include "lib/jxl/base/cache_aligned.h"
#include "lib/jxl/base/compiler_specific.h"
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/padded_bytes.h"
#include "lib/jxl/base/profiler.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/base/thread_pool_internal.h"
#include "lib/jxl/codec_in_out.h"
#include "lib/jxl/common.h"
#include "lib/jxl/enc_cache.h"
#include "lib/jxl/enc_file.h"
#include "lib/jxl/enc_params.h"
#include "lib/jxl/frame_header.h"
#include "lib/jxl/image.h"
#include "lib/jxl/image_bundle.h"
#include "lib/jxl/modular/encoding/encoding.h"
#include "tools/args.h"
#include "tools/box/box.h"
#include "tools/cpu/cpu.h"
#include "tools/speed_stats.h"
namespace jpegxl {
namespace tools {
namespace {
static inline bool ParseSpeedTier(const char* arg, jxl::SpeedTier* out) {
return jxl::ParseSpeedTier(arg, out);
}
static inline bool ParseColorTransform(const char* arg,
jxl::ColorTransform* out) {
size_t value = 0;
bool ret = ParseUnsigned(arg, &value);
if (ret && value > 2) ret = false;
if (ret) *out = jxl::ColorTransform(value);
return ret;
}
static inline bool ParseIntensityTarget(const char* arg, float* out) {
return ParseFloat(arg, out) && *out > 0;
}
static inline bool ParsePhotonNoiseParameter(const char* arg, float* out) {
return strncmp(arg, "ISO", 3) == 0 && ParseFloat(arg + 3, out) && *out > 0;
}
// Proposes a distance to try for a given bpp target. This could depend
// on the entropy in the image, too, but let's start with something.
static double ApproximateDistanceForBPP(double bpp) {
return 1.704 * pow(bpp, -0.804);
}
jxl::Status LoadSaliencyMap(const std::string& filename_heatmap,
jxl::ThreadPool* pool, jxl::ImageF* out_map) {
jxl::CodecInOut io_heatmap;
if (!SetFromFile(filename_heatmap, jxl::ColorHints(), &io_heatmap, pool)) {
return JXL_FAILURE("Could not load heatmap.");
}
*out_map = std::move(io_heatmap.Main().color()->Plane(0));
return true;
}
// Search algorithm for modular mode instead of Butteraugli distance.
void SetModularQualityForBitrate(jxl::ThreadPoolInternal* pool,
const size_t pixels, const double target_size,
CompressArgs* args) {
JXL_ASSERT(args->params.modular_mode);
CompressArgs s = *args; // Args for search.
float quality = -100 + target_size * 8.0 / pixels * 50;
if (quality > 100.f) quality = 100.f;
s.params.target_size = 0;
s.params.target_bitrate = 0;
double best_loss = 1e99;
float best_quality = quality;
float best_below = -10000.f;
float best_below_size = 0;
float best_above = 200.f;
float best_above_size = pixels * 15.f;
jxl::CodecInOut io;
double decode_mps = 0;
if (!LoadAll(*args, pool, &io, &decode_mps)) {
s.params.quality_pair = std::make_pair(quality, quality);
printf("couldn't load image\n");
return;
}
for (int i = 0; i < 10; ++i) {
s.params.quality_pair = std::make_pair(quality, quality);
jxl::PaddedBytes candidate;
bool ok =
CompressJxl(io, decode_mps, pool, s, &candidate, /*print_stats=*/false);
if (!ok) {
printf(
"Compression error occurred during the search for best size."
" Trying with quality %.1f\n",
quality);
break;
}
printf("Quality %.2f yields %6zu bytes, %.3f bpp.\n", quality,
candidate.size(), candidate.size() * 8.0 / pixels);
const double ratio = static_cast<double>(candidate.size()) / target_size;
const double loss = std::abs(1.0 - ratio);
if (best_loss > loss) {
best_quality = quality;
best_loss = loss;
if (loss < 0.01f) break;
}
if (quality == 100.f && ratio < 1.f) break; // can't spend more bits
if (ratio > 1.f && quality < best_above) {
best_above = quality;
best_above_size = candidate.size();
}
if (ratio < 1.f && quality > best_below) {
best_below = quality;
best_below_size = candidate.size();
}
float t =
(target_size - best_below_size) / (best_above_size - best_below_size);
if (best_above > 100.f && ratio < 1.f) {
quality = (quality + 105) / 2;
} else if (best_above - best_below > 1000 && ratio > 1.f) {
quality -= 1000;
} else {
quality = best_above * t + best_below * (1.f - t);
}
if (quality >= 100.f) quality = 100.f;
}
args->params.quality_pair = std::make_pair(best_quality, best_quality);
args->params.target_bitrate = 0;
args->params.target_size = 0;
}
void SetParametersForSizeOrBitrate(jxl::ThreadPoolInternal* pool,
const size_t pixels, CompressArgs* args) {
CompressArgs s = *args; // Args for search.
// If fixed size, convert to bitrate.
if (s.params.target_size > 0) {
s.params.target_bitrate = s.params.target_size * 8.0 / pixels;
s.params.target_size = 0;
}
const double target_size = s.params.target_bitrate * (1 / 8.) * pixels;
if (args->params.modular_mode) {
SetModularQualityForBitrate(pool, pixels, target_size, args);
return;
}
double dist = ApproximateDistanceForBPP(s.params.target_bitrate);
s.params.target_bitrate = 0;
double best_dist = 1.0;
double best_loss = 1e99;
jxl::CodecInOut io;
double decode_mps = 0;
if (!LoadAll(*args, pool, &io, &decode_mps)) {
s.params.butteraugli_distance = static_cast<float>(dist);
printf("couldn't load image\n");
return;
}
for (int i = 0; i < 7; ++i) {
s.params.butteraugli_distance = static_cast<float>(dist);
jxl::PaddedBytes candidate;
bool ok =
CompressJxl(io, decode_mps, pool, s, &candidate, /*print_stats=*/false);
if (!ok) {
printf(
"Compression error occurred during the search for best size. "
"Trying with butteraugli distance %.15g\n",
best_dist);
break;
}
printf("Butteraugli distance %.3f yields %6zu bytes, %.3f bpp.\n", dist,
candidate.size(), candidate.size() * 8.0 / pixels);
const double ratio = static_cast<double>(candidate.size()) / target_size;
const double loss = std::max(ratio, 1.0 / std::max(ratio, 1e-30));
if (best_loss > loss) {
best_dist = dist;
best_loss = loss;
}
dist *= ratio;
if (dist < 0.01) {
dist = 0.01;
}
if (dist >= 16.0) {
dist = 16.0;
}
}
args->params.butteraugli_distance = static_cast<float>(best_dist);
args->params.target_bitrate = 0;
args->params.target_size = 0;
}
const char* ModeFromArgs(const CompressArgs& args) {
if (args.jpeg_transcode) return "JPEG";
if (args.params.modular_mode) return "Modular";
return "VarDCT";
}
std::string QualityFromArgs(const CompressArgs& args) {
char buf[100];
if (args.jpeg_transcode) {
snprintf(buf, sizeof(buf), "lossless transcode");
} else if (args.params.modular_mode) {
if (args.params.quality_pair.first == 100 &&
args.params.quality_pair.second == 100) {
snprintf(buf, sizeof(buf), "lossless");
} else if (args.params.quality_pair.first !=
args.params.quality_pair.second) {
snprintf(buf, sizeof(buf), "Q%.2f,%.2f", args.params.quality_pair.first,
args.params.quality_pair.second);
} else {
snprintf(buf, sizeof(buf), "Q%.2f", args.params.quality_pair.first);
}
} else {
snprintf(buf, sizeof(buf), "d%.3f", args.params.butteraugli_distance);
}
return buf;
}
void PrintMode(jxl::ThreadPoolInternal* pool, const jxl::CodecInOut& io,
const double decode_mps, const CompressArgs& args) {
const char* mode = ModeFromArgs(args);
const char* speed = SpeedTierName(args.params.speed_tier);
const std::string quality = QualityFromArgs(args);
fprintf(stderr,
"Read %zux%zu image, %.1f MP/s\n"
"Encoding [%s%s, %s, %s",
io.xsize(), io.ysize(), decode_mps,
(args.use_container ? "Container | " : ""), mode, quality.c_str(),
speed);
if (args.use_container) {
if (args.jpeg_transcode) fprintf(stderr, " | JPEG reconstruction data");
if (!io.blobs.exif.empty())
fprintf(stderr, " | %zu-byte Exif", io.blobs.exif.size());
if (!io.blobs.xmp.empty())
fprintf(stderr, " | %zu-byte XMP", io.blobs.xmp.size());
if (!io.blobs.jumbf.empty())
fprintf(stderr, " | %zu-byte JUMBF", io.blobs.jumbf.size());
}
fprintf(stderr, "], %zu threads.\n", pool->NumWorkerThreads());
}
} // namespace
void CompressArgs::AddCommandLineOptions(CommandLineParser* cmdline) {
// Positional arguments.
cmdline->AddPositionalOption("INPUT", /* required = */ true,
"the input can be PNG"
#if JPEGXL_ENABLE_APNG
", APNG"
#endif
#if JPEGXL_ENABLE_GIF
", GIF"
#endif
#if JPEGXL_ENABLE_JPEG
", JPEG"
#endif
#if JPEGXL_ENABLE_EXR
", EXR"
#endif
", PPM, PFM, or PGX",
&file_in);
cmdline->AddPositionalOption(
"OUTPUT", /* required = */ true,
"the compressed JXL output file (can be omitted for benchmarking)",
&file_out);
// Flags.
// TODO(lode): also add options to add exif/xmp/other metadata in the
// container.
// TODO(lode): decide on good name for this flag: box, container, bmff, ...
cmdline->AddOptionFlag(
'\0', "container",
"Always encode using container format (default: only if needed)",
&use_container, &SetBooleanTrue, 1);
cmdline->AddOptionFlag('\0', "strip",
"Do not encode using container format (strips "
"Exif/XMP/JPEG bitstream reconstruction data)",
&no_container, &SetBooleanTrue, 2);
// Target distance/size/bpp
opt_distance_id = cmdline->AddOptionValue(
'd', "distance", "maxError",
("Max. butteraugli distance, lower = higher quality. Range: 0 .. 25.\n"
" 0.0 = mathematically lossless. Default for already-lossy input "
"(JPEG/GIF).\n"
" 1.0 = visually lossless. Default for other input.\n"
" Recommended range: 0.5 .. 3.0."),
&params.butteraugli_distance, &ParseFloat);
opt_target_size_id = cmdline->AddOptionValue(
'\0', "target_size", "N",
("Aim at file size of N bytes.\n"
" Compresses to 1 % of the target size in ideal conditions.\n"
" Runs the same algorithm as --target_bpp"),
&params.target_size, &ParseUnsigned, 1);
opt_target_bpp_id = cmdline->AddOptionValue(
'\0', "target_bpp", "BPP",
("Aim at file size that has N bits per pixel.\n"
" Compresses to 1 % of the target BPP in ideal conditions."),
&params.target_bitrate, &ParseFloat, 1);
// High-level options
opt_quality_id = cmdline->AddOptionValue(
'q', "quality", "QUALITY",
"Quality setting (is remapped to --distance). Range: -inf .. 100.\n"
" 100 = mathematically lossless. Default for already-lossy input "
"(JPEG/GIF).\n Positive quality values roughly match libjpeg quality.",
&quality, &ParseFloat);
cmdline->AddOptionValue(
'e', "effort", "EFFORT",
"Encoder effort setting. Range: 1 .. 9.\n"
" Default: 7. Higher number is more effort (slower).",
&params.speed_tier, &ParseSpeedTier);
cmdline->AddOptionValue(
's', "speed", "ANIMAL",
"Deprecated synonym for --effort. Valid values are:\n"
" lightning (1), thunder, falcon, cheetah, hare, wombat, squirrel, "
"kitten, tortoise (9)\n"
" Default: squirrel. Values are in order from faster to slower.\n",
&params.speed_tier, &ParseSpeedTier, 2);
cmdline->AddOptionValue('\0', "faster_decoding", "AMOUNT",
"Favour higher decoding speed. 0 = default, higher "
"values give higher speed at the expense of quality",
&params.decoding_speed_tier, &ParseUnsigned, 2);
cmdline->AddOptionFlag('p', "progressive",
"Enable progressive/responsive decoding.",
&progressive, &SetBooleanTrue);
cmdline->AddOptionFlag('\0', "premultiply",
"Force premultiplied (associated) alpha.",
&force_premultiplied, &SetBooleanTrue, 1);
cmdline->AddOptionValue('\0', "keep_invisible", "0|1",
"force disable/enable preserving color of invisible "
"pixels (default: 1 if lossless, 0 if lossy).",
&params.keep_invisible, &ParseOverride, 1);
cmdline->AddOptionFlag('\0', "centerfirst",
"Put center groups first in the compressed file.",
&params.centerfirst, &SetBooleanTrue, 1);
cmdline->AddOptionValue('\0', "center_x", "0..XSIZE",
"Put center groups first in the compressed file.",
&params.center_x, &ParseUnsigned, 1);
cmdline->AddOptionValue('\0', "center_y", "0..YSIZE",
"Put center groups first in the compressed file.",
&params.center_y, &ParseUnsigned, 1);
// Flags.
cmdline->AddOptionFlag('\0', "progressive_ac",
"Use the progressive mode for AC.",
&params.progressive_mode, &SetBooleanTrue, 1);
cmdline->AddOptionFlag('\0', "qprogressive_ac",
"Use the progressive mode for AC.",
&params.qprogressive_mode, &SetBooleanTrue, 1);
cmdline->AddOptionValue('\0', "progressive_dc", "num_dc_frames",
"Use progressive mode for DC.",
&params.progressive_dc, &ParseSigned, 1);
cmdline->AddOptionFlag('m', "modular",
"Use the modular mode (lossy / lossless).",
&params.modular_mode, &SetBooleanTrue, 1);
cmdline->AddOptionFlag('\0', "use_new_heuristics",
"use new and not yet ready encoder heuristics",
&params.use_new_heuristics, &SetBooleanTrue, 2);
// JPEG modes: parallel Brunsli, pixels to JPEG, or JPEG to Brunsli
cmdline->AddOptionFlag('j', "jpeg_transcode",
"Do lossy transcode of input JPEG file (decode to "
"pixels instead of doing lossless transcode).",
&jpeg_transcode, &SetBooleanFalse, 1);
opt_num_threads_id = cmdline->AddOptionValue(
'\0', "num_threads", "N", "number of worker threads (zero = none).",
&num_threads, &ParseUnsigned, 1);
cmdline->AddOptionValue('\0', "num_reps", "N", "how many times to compress.",
&num_reps, &ParseUnsigned, 1);
cmdline->AddOptionValue('\0', "noise", "0|1",
"force disable/enable noise generation.",
&params.noise, &ParseOverride, 1);
cmdline->AddOptionValue(
'\0', "photon_noise", "ISO3200",
"Set the noise to approximately what it would be at a given nominal "
"exposure on a 35mm camera. For formats other than 35mm, or when the "
"whole sensor was not used, you can multiply the ISO value by the "
"equivalence ratio squared, for example by 2.25 for an APS-C camera.",
&params.photon_noise_iso, &ParsePhotonNoiseParameter, 1);
cmdline->AddOptionValue('\0', "dots", "0|1",
"force disable/enable dots generation.", &params.dots,
&ParseOverride, 1);
cmdline->AddOptionValue('\0', "patches", "0|1",
"force disable/enable patches generation.",
&params.patches, &ParseOverride, 1);
cmdline->AddOptionValue(
'\0', "resampling", "0|1|2|4|8",
"Subsample all color channels by this factor, or use 0 to choose the "
"resampling factor based on distance.",
&params.resampling, &ParseUnsigned, 0);
cmdline->AddOptionValue(
'\0', "ec_resampling", "1|2|4|8",
"Subsample all extra channels by this factor. If this value is smaller "
"than the resampling of color channels, it will be increased to match.",
&params.ec_resampling, &ParseUnsigned, 2);
cmdline->AddOptionFlag('\0', "already_downsampled",
"Do not downsample the given input before encoding, "
"but still signal that the decoder should upsample.",
&params.already_downsampled, &SetBooleanTrue, 2);
cmdline->AddOptionValue(
'\0', "epf", "-1..3",
"Edge preserving filter level (-1 = choose based on quality, default)",
&params.epf, &ParseSigned, 1);
cmdline->AddOptionValue('\0', "gaborish", "0|1",
"force disable/enable gaborish.", &params.gaborish,
&ParseOverride, 1);
opt_intensity_target_id = cmdline->AddOptionValue(
'\0', "intensity_target", "N",
("Intensity target of monitor in nits, higher\n"
" results in higher quality image. Must be strictly positive.\n"
" Default is 255 for standard images, 4000 for input images known to\n"
" to have PQ or HLG transfer function."),
&intensity_target, &ParseIntensityTarget, 1);
cmdline->AddOptionValue('\0', "saliency_num_progressive_steps", "N", nullptr,
&params.saliency_num_progressive_steps,
&ParseUnsigned, 2);
cmdline->AddOptionValue('\0', "saliency_map_filename", "STRING", nullptr,
&saliency_map_filename, &ParseString, 2);
cmdline->AddOptionValue('\0', "saliency_threshold", "0..1", nullptr,
&params.saliency_threshold, &ParseFloat, 2);
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, &ParseAndAppendKeyValue, 1);
cmdline->AddOptionValue(
'\0', "override_bitdepth", "0=use from image, 1-32=override",
"If nonzero, store the given bit depth in the JPEG XL file metadata"
" (1-32), instead of using the bit depth from the original input"
" image.",
&override_bitdepth, &ParseUnsigned, 2);
opt_color_id = cmdline->AddOptionValue(
'c', "colortransform", "0..2", "0=XYB, 1=None, 2=YCbCr",
&params.color_transform, &ParseColorTransform, 2);
// modular mode options
cmdline->AddOptionValue(
'Q', "mquality", "luma_q[,chroma_q]",
"[modular encoding] lossy 'quality' (100=lossless, lower is more lossy)",
&params.quality_pair, &ParseFloatPair, 1);
cmdline->AddOptionValue(
'I', "iterations", "F",
"[modular encoding] fraction of pixels used to learn MA trees "
"(default=0.5, try 0 for no MA and fast decode)",
&params.options.nb_repeats, &ParseFloat, 2);
cmdline->AddOptionValue(
'C', "colorspace", "K",
("[modular encoding] color transform: 0=RGB, 1=YCoCg, "
"2-37=RCT (default: try several, depending on speed)"),
&params.colorspace, &ParseSigned, 1);
opt_m_group_size_id = cmdline->AddOptionValue(
'g', "group-size", "K",
("[modular encoding] set group size to 128 << K "
"(default: 1 or 2)"),
&params.modular_group_size_shift, &ParseUnsigned, 1);
cmdline->AddOptionValue(
'P', "predictor", "K",
"[modular encoding] predictor(s) to use: 0=zero, "
"1=left, 2=top, 3=avg0, 4=select, 5=gradient, 6=weighted, "
"7=topright, 8=topleft, 9=leftleft, 10=avg1, 11=avg2, 12=avg3, "
"13=toptop predictive average "
"14=mix 5 and 6, 15=mix everything. Default 14, at slowest speed "
"default 15",
&params.options.predictor, &ParsePredictor, 1);
cmdline->AddOptionValue(
'E', "extra-properties", "K",
"[modular encoding] number of extra MA tree properties to use",
&params.options.max_properties, &ParseSigned, 2);
cmdline->AddOptionValue('\0', "palette", "K",
"[modular encoding] use a palette if image has at "
"most K colors (default: 1024)",
&params.palette_colors, &ParseSigned, 1);
cmdline->AddOptionFlag(
'\0', "lossy-palette",
"[modular encoding] quantize to a palette that has fewer entries than "
"would be necessary for perfect preservation; for the time being, it is "
"recommended to set --palette=0 with this option to use the default "
"palette only",
&params.lossy_palette, &SetBooleanTrue, 1);
cmdline->AddOptionValue(
'X', "pre-compact", "PERCENT",
("[modular encoding] compact channels (globally) if ratio "
"used/range is below this (default: 80%)"),
&params.channel_colors_pre_transform_percent, &ParseFloat, 2);
cmdline->AddOptionValue(
'Y', "post-compact", "PERCENT",
("[modular encoding] compact channels (per-group) if ratio "
"used/range is below this (default: 80%)"),
&params.channel_colors_percent, &ParseFloat, 2);
cmdline->AddOptionValue('R', "responsive", "K",
"[modular encoding] do Squeeze transform, 0=false, "
"1=true (default: true if lossy, false if lossless)",
&params.responsive, &ParseSigned, 1);
cmdline->AddOptionFlag('V', "version", "Print version number and exit",
&version, &SetBooleanTrue, 1);
cmdline->AddOptionFlag('\0', "quiet", "Be more silent", &quiet,
&SetBooleanTrue, 1);
cmdline->AddOptionValue('\0', "print_profile", "0|1",
"Print timing information before exiting",
&print_profile, &ParseOverride, 1);
cmdline->AddOptionFlag(
'v', "verbose",
"Verbose output; can be repeated, also applies to help (!).",
&params.verbose, &SetBooleanTrue);
}
jxl::Status CompressArgs::ValidateArgs(const CommandLineParser& cmdline) {
params.file_in = file_in;
params.file_out = file_out;
if (file_in == nullptr) {
fprintf(stderr, "Missing INPUT filename.\n");
return false;
}
bool got_distance = cmdline.GetOption(opt_distance_id)->matched();
bool got_target_size = cmdline.GetOption(opt_target_size_id)->matched();
bool got_target_bpp = cmdline.GetOption(opt_target_bpp_id)->matched();
bool got_quality = cmdline.GetOption(opt_quality_id)->matched();
bool got_intensity_target =
cmdline.GetOption(opt_intensity_target_id)->matched();
if (got_quality) {
default_settings = false;
if (quality < 100) jpeg_transcode = false;
// Quality settings roughly match libjpeg qualities.
if (quality < 7 || quality == 100 || params.modular_mode) {
if (jpeg_transcode == false) params.modular_mode = true;
// Internal modular quality to roughly match VarDCT size.
if (quality < 7) {
params.quality_pair.first = params.quality_pair.second =
std::min(35 + (quality - 7) * 3.0f, 100.0f);
} else {
params.quality_pair.first = params.quality_pair.second =
std::min(35 + (quality - 7) * 65.f / 93.f, 100.0f);
}
} else {
if (quality >= 30) {
params.butteraugli_distance = 0.1 + (100 - quality) * 0.09;
} else {
params.butteraugli_distance =
6.4 + pow(2.5, (30 - quality) / 5.0f) / 6.25f;
}
}
}
if (params.resampling > 1 && !params.already_downsampled)
jpeg_transcode = false;
if (progressive) {
params.qprogressive_mode = true;
params.responsive = 1;
default_settings = false;
}
if (got_target_size || got_target_bpp || got_intensity_target) {
default_settings = false;
}
if (params.progressive_dc < -1 || params.progressive_dc > 2) {
fprintf(stderr, "Invalid/out of range progressive_dc (%d), try -1 to 2.\n",
params.progressive_dc);
return false;
}
if (got_distance) {
constexpr float butteraugli_min_dist = 0.1f;
constexpr float butteraugli_max_dist = 25.0f;
if (!(0 <= params.butteraugli_distance &&
params.butteraugli_distance <= butteraugli_max_dist)) {
fprintf(stderr, "Invalid/out of range distance, try 0 to %g.\n",
butteraugli_max_dist);
return false;
}
if (params.butteraugli_distance > 0) jpeg_transcode = false;
if (params.butteraugli_distance == 0) {
// Use modular for lossless.
if (jpeg_transcode == false) params.modular_mode = true;
} else if (params.butteraugli_distance < butteraugli_min_dist) {
params.butteraugli_distance = butteraugli_min_dist;
}
default_settings = false;
}
if (got_target_bpp || got_target_size) {
jpeg_transcode = false;
}
if (got_target_bpp + got_target_size + got_distance + got_quality > 1) {
fprintf(stderr,
"You can specify only one of '--distance', '-q', "
"'--target_bpp' and '--target_size'. They are all different ways"
" to specify the image quality. When in doubt, use --distance."
" It gives the most visually consistent results.\n");
return false;
}
if (!saliency_map_filename.empty()) {
if (!params.progressive_mode) {
saliency_map_filename.clear();
fprintf(stderr,
"Warning: Specifying --saliency_map_filename only makes sense "
"for --progressive_ac mode.\n");
}
}
if (!params.file_in) {
fprintf(stderr, "Missing input filename.\n");
return false;
}
if (!cmdline.GetOption(opt_color_id)->matched()) {
// default to RGB for lossless modular
if (params.modular_mode) {
if (params.quality_pair.first != 100 ||
params.quality_pair.second != 100) {
params.color_transform = jxl::ColorTransform::kXYB;
} else {
params.color_transform = jxl::ColorTransform::kNone;
}
}
}
if (override_bitdepth > 32) {
fprintf(stderr, "override_bitdepth must be <= 32\n");
return false;
}
if (params.epf > 3) {
fprintf(stderr, "--epf must be in the 0..3 range\n");
return false;
}
// User didn't override num_threads, so we have to compute a default, which
// might fail, so only do so when necessary. Don't just check num_threads != 0
// because the user may have set it to that.
if (!cmdline.GetOption(opt_num_threads_id)->matched()) {
cpu::ProcessorTopology topology;
if (!cpu::DetectProcessorTopology(&topology)) {
// We have seen sporadic failures caused by setaffinity_np.
fprintf(stderr,
"Failed to choose default num_threads; you can avoid this "
"error by specifying a --num_threads N argument.\n");
return false;
}
num_threads = topology.packages * topology.cores_per_package;
}
return true;
}
jxl::Status CompressArgs::ValidateArgsAfterLoad(
const CommandLineParser& cmdline, const jxl::CodecInOut& io) {
if (!ValidateArgs(cmdline)) return false;
bool got_m_group_size = cmdline.GetOption(opt_m_group_size_id)->matched();
if (params.modular_mode && !got_m_group_size) {
// Default modular group size: set to 512 if 256 would be silly
const size_t kThinImageThr = 256 + 64;
const size_t kSmallImageThr = 256 + 128;
if (io.xsize() < kThinImageThr || io.ysize() < kThinImageThr ||
(io.xsize() < kSmallImageThr && io.ysize() < kSmallImageThr)) {
params.modular_group_size_shift = 2;
}
}
if (!io.blobs.exif.empty() || !io.blobs.xmp.empty() ||
!io.blobs.jumbf.empty() || !io.blobs.iptc.empty() || jpeg_transcode) {
use_container = true;
}
if (no_container) use_container = false;
if (jpeg_transcode && params.modular_mode) {
fprintf(stderr,
"Error: cannot do lossless JPEG transcode in modular mode.\n");
return false;
}
if (jpeg_transcode) {
if (params.progressive_mode || params.qprogressive_mode ||
params.progressive_dc > 0) {
fprintf(stderr,
"Error: progressive lossless JPEG transcode is not yet "
"implemented.\n");
return false;
}
}
return true;
}
jxl::Status LoadAll(CompressArgs& args, jxl::ThreadPoolInternal* pool,
jxl::CodecInOut* io, double* decode_mps) {
const double t0 = jxl::Now();
io->target_nits = args.intensity_target;
io->dec_target = (args.jpeg_transcode ? jxl::DecodeTarget::kQuantizedCoeffs
: jxl::DecodeTarget::kPixels);
jxl::Codec input_codec;
if (!SetFromFile(args.params.file_in, args.color_hints, io, nullptr,
&input_codec)) {
fprintf(stderr, "Failed to read image %s.\n", args.params.file_in);
return false;
}
if (input_codec != jxl::Codec::kJPG) args.jpeg_transcode = false;
if (args.jpeg_transcode) args.params.butteraugli_distance = 0;
if (input_codec == jxl::Codec::kGIF && args.default_settings) {
args.params.modular_mode = true;
args.params.quality_pair.first = args.params.quality_pair.second = 100;
}
if (args.override_bitdepth != 0) {
if (args.override_bitdepth == 32) {
io->metadata.m.SetFloat32Samples();
} else {
io->metadata.m.SetUintSamples(args.override_bitdepth);
}
}
if (args.force_premultiplied) {
io->PremultiplyAlpha();
}
jxl::ImageF saliency_map;
if (!args.saliency_map_filename.empty()) {
if (!LoadSaliencyMap(args.saliency_map_filename, pool, &saliency_map)) {
fprintf(stderr, "Failed to read saliency map %s.\n",
args.saliency_map_filename.c_str());
return false;
}
args.params.saliency_map = &saliency_map;
}
const double t1 = jxl::Now();
const size_t pixels = io->xsize() * io->ysize();
*decode_mps = pixels * io->frames.size() * 1E-6 / (t1 - t0);
return true;
}
jxl::Status CompressJxl(jxl::CodecInOut& io, double decode_mps,
jxl::ThreadPoolInternal* pool, CompressArgs& args,
jxl::PaddedBytes* compressed, bool print_stats) {
JXL_CHECK(pool);
const size_t pixels = io.xsize() * io.ysize();
if (args.params.target_size > 0 || args.params.target_bitrate > 0) {
// Slow iterative search for parameters that reach target bpp / size.
SetParametersForSizeOrBitrate(pool, pixels, &args);
}
if (print_stats) PrintMode(pool, io, decode_mps, args);
// Final/actual compression run (possibly repeated for benchmarking).
jxl::AuxOut aux_out;
if (args.inspector_image3f) {
aux_out.SetInspectorImage3F(args.inspector_image3f);
}
SpeedStats stats;
jxl::PassesEncoderState passes_encoder_state;
if (args.params.use_new_heuristics) {
passes_encoder_state.heuristics =
jxl::make_unique<jxl::FastEncoderHeuristics>();
}
for (size_t i = 0; i < args.num_reps; ++i) {
const double t0 = jxl::Now();
jxl::Status ok = false;
if (io.Main().IsJPEG()) {
// TODO(lode): automate this in the encoder. The encoder must in the
// beginning choose to either do all in xyb, or all in non-xyb, write
// that in the xyb_encoded header flag, and persistently keep that state
// to check if every frame uses an allowed color transform.
args.params.color_transform = io.Main().color_transform;
}
ok = EncodeFile(args.params, &io, &passes_encoder_state, compressed,
&aux_out, pool);
if (!ok) {
fprintf(stderr, "Failed to compress to %s.\n", ModeFromArgs(args));
return false;
}
const double t1 = jxl::Now();
stats.NotifyElapsed(t1 - t0);
stats.SetImageSize(io.xsize(), io.ysize());
}
if (print_stats) {
const double bpp =
static_cast<double>(compressed->size() * jxl::kBitsPerByte) / pixels;
fprintf(stderr, "Compressed to %zu bytes (%.3f bpp%s).\n",
compressed->size(), bpp / io.frames.size(),
io.frames.size() == 1 ? "" : "/frame");
JXL_CHECK(stats.Print(args.num_threads));
if (args.params.verbose) {
aux_out.Print(1);
}
}
return true;
}
} // namespace tools
} // namespace jpegxl