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

322 lines
12 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/djxl.h"
#include <stdio.h>
#include "lib/extras/codec.h"
#include "lib/extras/codec_jpg.h"
#include "lib/extras/color_description.h"
#include "lib/extras/time.h"
#include "lib/extras/tone_mapping.h"
#include "lib/jxl/alpha.h"
#include "lib/jxl/base/data_parallel.h"
#include "lib/jxl/base/file_io.h"
#include "lib/jxl/base/override.h"
#include "lib/jxl/color_encoding_internal.h"
#include "lib/jxl/color_management.h"
#include "lib/jxl/dec_file.h"
#include "lib/jxl/frame_header.h"
#include "lib/jxl/image.h"
#include "lib/jxl/image_bundle.h"
#include "lib/jxl/image_ops.h"
#include "tools/args.h"
#include "tools/box/box.h"
#include "tools/cpu/cpu.h"
namespace jpegxl {
namespace tools {
static inline bool ParseLuminanceRange(const char* arg,
std::pair<float, float>* out) {
char* end;
out->first = static_cast<float>(strtod(arg, &end));
if (*end == '\0') {
// That was actually the upper bound.
out->second = out->first;
out->first = 0;
return true;
}
if (*end != '-') {
fprintf(stderr, "Unable to interpret as luminance range: %s.\n", arg);
return JXL_FAILURE("Args");
}
const char* second = end + 1;
out->second = static_cast<float>(strtod(second, &end));
if (*end != '\0') {
fprintf(stderr, "Unable to interpret as luminance range: %s.\n", arg);
return JXL_FAILURE("Args");
}
return true;
}
void DecompressArgs::AddCommandLineOptions(CommandLineParser* cmdline) {
// Positional arguments.
cmdline->AddPositionalOption("INPUT", /* required = */ true,
"the compressed input file", &file_in);
cmdline->AddPositionalOption(
"OUTPUT", /* required = */ true,
"the output can be PNG with ICC, JPG, or PPM/PFM.", &file_out);
cmdline->AddOptionFlag('V', "version", "print version number and exit",
&version, &SetBooleanTrue);
cmdline->AddOptionValue('\0', "num_reps", "N", nullptr, &num_reps,
&ParseUnsigned);
opt_num_threads_id = cmdline->AddOptionValue('\0', "num_threads", "N",
"The number of threads to use",
&num_threads, &ParseUnsigned);
cmdline->AddOptionValue('\0', "print_profile", "0|1",
"print timing information before exiting",
&print_profile, &ParseOverride);
cmdline->AddOptionValue('\0', "bits_per_sample", "N",
"defaults to original (input) bit depth",
&bits_per_sample, &ParseUnsigned);
cmdline->AddOptionFlag(
'\0', "tone_map",
"tone map the image to the luminance range indicated by --display_nits "
"instead of performing a naive 0-1 -> 0-1 conversion",
&tone_map, &SetBooleanTrue);
cmdline->AddOptionValue('\0', "display_nits", "0.3-250",
"luminance range of the display to which to "
"tone-map; the lower bound can be omitted",
&display_nits, &ParseLuminanceRange);
cmdline->AddOptionValue('\0', "color_space", "RGB_D65_SRG_Rel_Lin",
"defaults to original (input) color space",
&color_space, &ParseString);
cmdline->AddOptionValue('s', "downsampling", "1,2,4,8,16",
"maximum permissible downsampling factor (values "
"greater than 16 will return the LQIP if available)",
&params.max_downsampling, &ParseUnsigned);
cmdline->AddOptionFlag('\0', "allow_partial_files",
"allow decoding of truncated files",
&params.allow_partial_files, &SetBooleanTrue);
cmdline->AddOptionFlag('\0', "allow_more_progressive_steps",
"allow decoding more progressive steps in truncated "
"files. No effect without --allow_partial_files",
&params.allow_more_progressive_steps, &SetBooleanTrue);
#if JPEGXL_ENABLE_JPEG
cmdline->AddOptionFlag(
'j', "pixels_to_jpeg",
"By default, if the input JPEG XL contains a recompressed JPEG file, "
"djxl "
"reconstructs the exact original JPEG file. This flag causes the decoder "
"to instead decode the image to pixels and encode a new (lossy) JPEG. "
"The output file if provided must be a .jpg or .jpeg file.",
&decode_to_pixels, &SetBooleanTrue);
opt_jpeg_quality_id =
cmdline->AddOptionValue('q', "jpeg_quality", "N",
"JPEG output quality. Setting an output quality "
"implies --pixels_to_jpeg.",
&jpeg_quality, &ParseUnsigned);
#endif
#if JPEGXL_ENABLE_SJPEG
cmdline->AddOptionFlag('\0', "use_sjpeg",
"use sjpeg instead of libjpeg for JPEG output",
&use_sjpeg, &SetBooleanTrue);
#endif
cmdline->AddOptionFlag('\0', "print_read_bytes",
"print total number of decoded bytes",
&print_read_bytes, &SetBooleanTrue);
cmdline->AddOptionFlag('\0', "quiet", "silence output (except for errors)",
&quiet, &SetBooleanTrue);
}
jxl::Status DecompressArgs::ValidateArgs(const CommandLineParser& cmdline) {
if (file_in == nullptr) {
fprintf(stderr, "Missing INPUT filename.\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;
}
#if JPEGXL_ENABLE_JPEG
if (cmdline.GetOption(opt_jpeg_quality_id)->matched()) {
decode_to_pixels = true;
}
#endif
if (file_out) {
const std::string extension = jxl::Extension(file_out);
const jxl::Codec codec =
jxl::CodecFromExtension(extension, &bits_per_sample);
if (codec != jxl::Codec::kJPG) {
// when decoding to anything-but-JPEG, we'll need pixels
decode_to_pixels = true;
}
} else {
decode_to_pixels = true;
}
return true;
}
jxl::Status DecompressJxlToPixels(const jxl::Span<const uint8_t> compressed,
const jxl::DecompressParams& params,
jxl::ThreadPool* pool,
jxl::CodecInOut* JXL_RESTRICT io,
SpeedStats* JXL_RESTRICT stats) {
const double t0 = jxl::Now();
if (!jxl::DecodeFile(params, compressed, io, pool)) {
fprintf(stderr, "Failed to decompress to pixels.\n");
return false;
}
const double t1 = jxl::Now();
stats->NotifyElapsed(t1 - t0);
stats->SetImageSize(io->xsize(), io->ysize());
return true;
}
jxl::Status DecompressJxlToJPEG(const JpegXlContainer& container,
const DecompressArgs& args,
jxl::ThreadPool* pool, jxl::PaddedBytes* output,
SpeedStats* JXL_RESTRICT stats) {
output->clear();
const double t0 = jxl::Now();
jxl::Span<const uint8_t> compressed(container.codestream,
container.codestream_size);
JXL_RETURN_IF_ERROR(compressed.size() >= 2);
// JXL case
// Decode to DCT when possible and generate a JPG file.
jxl::CodecInOut io;
// Set JPEG quality.
// TODO(deymo): We should probably fail to give a JPEG file if the
// original image can't be transcoded to a JPEG file without passing
// through pixels, or at least signal this to the user.
io.use_sjpeg = args.use_sjpeg;
io.jpeg_quality = args.jpeg_quality;
if (!DecodeJpegXlToJpeg(args.params, container, &io, pool)) {
return JXL_FAILURE("Failed to decode JXL to JPEG");
}
if (!jxl::extras::EncodeImageJPGCoefficients(&io, output)) {
return JXL_FAILURE("Failed to generate JPEG");
}
stats->SetImageSize(io.xsize(), io.ysize());
const double t1 = jxl::Now();
stats->NotifyElapsed(t1 - t0);
stats->SetFileSize(output->size());
return true;
}
jxl::Status WriteJxlOutput(const DecompressArgs& args, const char* file_out,
jxl::CodecInOut& io, jxl::ThreadPool* pool) {
// Can only write if we decoded and have an output filename.
// (Writing large PNGs is slow, so allow skipping it for benchmarks.)
if (file_out == nullptr) return true;
// Override original color space with arg if specified.
jxl::ColorEncoding c_out = io.metadata.m.color_encoding;
if (!args.color_space.empty()) {
bool color_space_applied = false;
JxlColorEncoding c_out_external;
if (jxl::ParseDescription(args.color_space, &c_out_external) &&
ConvertExternalToInternalColorEncoding(c_out_external, &c_out) &&
c_out.CreateICC()) {
color_space_applied = true;
} else {
jxl::PaddedBytes icc;
if (jxl::ReadFile(args.color_space, &icc) &&
c_out.SetICC(std::move(icc))) {
color_space_applied = true;
}
}
if (!color_space_applied) {
fprintf(stderr, "Failed to apply color_space.\n");
return false;
}
}
// Override original #bits with arg if specified.
size_t bits_per_sample = io.metadata.m.bit_depth.bits_per_sample;
if (args.bits_per_sample != 0) bits_per_sample = args.bits_per_sample;
if (args.tone_map) {
jxl::Status status = jxl::ToneMapTo(args.display_nits, &io, pool);
if (!status) fprintf(stderr, "Failed to map tones.\n");
JXL_RETURN_IF_ERROR(status);
if (c_out.tf.IsPQ() && args.color_space.empty()) {
// Prevent writing the tone-mapped image to PQ output unless explicitly
// requested. The result would look even dimmer than it would have without
// tone mapping.
c_out.tf.SetTransferFunction(jxl::TransferFunction::kSRGB);
status = c_out.CreateICC();
if (!status) fprintf(stderr, "Failed to create ICC\n");
JXL_RETURN_IF_ERROR(c_out.CreateICC());
}
}
if (!io.metadata.m.have_animation) {
if (!EncodeToFile(io, c_out, bits_per_sample, file_out, pool)) {
fprintf(stderr, "Failed to write decoded image.\n");
return false;
}
} else {
const char* extension = strrchr(file_out, '.');
std::string base = extension == nullptr
? std::string(file_out)
: std::string(file_out, extension - file_out);
if (extension == nullptr) extension = "";
const int digits = 1 + static_cast<int>(std::log10(std::max(
1, static_cast<int>(io.frames.size() - 1))));
std::vector<char> output_filename;
output_filename.resize(base.size() + 1 + digits + strlen(extension) + 1);
for (size_t i = 0; i < io.frames.size(); ++i) {
jxl::CodecInOut frame_io;
frame_io.SetFromImage(jxl::CopyImage(*io.frames[i].color()),
io.frames[i].c_current());
frame_io.metadata.m = *io.frames[i].metadata();
if (io.frames[i].HasAlpha()) {
frame_io.Main().SetAlpha(
jxl::CopyImage(*io.frames[i].alpha()),
/*alpha_is_premultiplied=*/io.frames[i].AlphaIsPremultiplied());
}
snprintf(output_filename.data(), output_filename.size(), "%s-%0*zu%s",
base.c_str(), digits, i, extension);
if (!EncodeToFile(frame_io, c_out, bits_per_sample,
output_filename.data(), pool)) {
fprintf(stderr, "Failed to write decoded image for frame %zu/%zu.\n",
i + 1, io.frames.size());
}
}
}
return true;
}
} // namespace tools
} // namespace jpegxl