blob: 17dcda42d076e6390e9c7c2cc5f397812df37b94 [file] [edit]
// 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 <jxl/cms.h>
#include <jxl/memory_manager.h>
#include <jxl/types.h>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <iostream>
#include <istream>
#include <memory>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include "lib/extras/codec_in_out.h"
#include "lib/jxl/base/common.h"
#include "lib/jxl/base/override.h"
#include "lib/jxl/base/span.h"
#include "lib/jxl/base/status.h"
#include "lib/jxl/cms/color_encoding_cms.h"
#include "lib/jxl/color_encoding_internal.h"
#include "lib/jxl/common.h"
#include "lib/jxl/enc_bit_writer.h"
#include "lib/jxl/enc_cache.h"
#include "lib/jxl/enc_fields.h"
#include "lib/jxl/enc_frame.h"
#include "lib/jxl/enc_params.h"
#include "lib/jxl/frame_dimensions.h"
#include "lib/jxl/frame_header.h"
#include "lib/jxl/image.h"
#include "lib/jxl/image_metadata.h"
#include "lib/jxl/modular/encoding/dec_ma.h"
#include "lib/jxl/modular/encoding/enc_debug_tree.h"
#include "lib/jxl/modular/options.h"
#include "lib/jxl/noise.h"
#include "lib/jxl/splines.h"
#include "lib/jxl/test_utils.h" // TODO(eustas): cut this dependency
#include "tools/file_io.h"
#include "tools/no_memory_manager.h"
namespace jpegxl {
namespace tools {
using ::jxl::BitWriter;
using ::jxl::BlendMode;
using ::jxl::CodecInOut;
using ::jxl::CodecMetadata;
using ::jxl::ColorEncoding;
using ::jxl::ColorTransform;
using ::jxl::CompressParams;
using ::jxl::FrameDimensions;
using ::jxl::FrameInfo;
using ::jxl::Image3F;
using ::jxl::ImageF;
using ::jxl::PaddedBytes;
using ::jxl::PassesEncoderState;
using ::jxl::Predictor;
using ::jxl::PropertyDecisionNode;
using ::jxl::QuantizedSpline;
using ::jxl::Span;
using ::jxl::Spline;
using ::jxl::Splines;
using ::jxl::Status;
using ::jxl::Tree;
namespace {
struct SplineData {
int32_t quantization_adjustment = 1;
std::vector<Spline> splines;
};
Status SplinesFromSplineData(const SplineData& spline_data,
std::vector<QuantizedSpline>& quantized_splines,
std::vector<Spline::Point>& starting_points) {
quantized_splines.clear();
starting_points.clear();
quantized_splines.reserve(spline_data.splines.size());
starting_points.reserve(spline_data.splines.size());
for (const Spline& spline : spline_data.splines) {
JXL_ASSIGN_OR_RETURN(
QuantizedSpline qspline,
QuantizedSpline::Create(spline, spline_data.quantization_adjustment,
0.0, 1.0));
quantized_splines.emplace_back(std::move(qspline));
starting_points.push_back(spline.control_points.front());
}
return true;
}
template <typename F>
bool ParseNode(F& tok, Tree& tree, SplineData& spline_data,
CompressParams& cparams, size_t& W, size_t& H, CodecInOut& io,
JXL_BOOL& have_next, int& x0, int& y0,
int& buffer_size) {
std::unordered_map<std::string, int> property_map = {
{"c", 0},
{"g", 1},
{"y", 2},
{"x", 3},
{"|N|", 4},
{"|W|", 5},
{"N", 6},
{"W", 7},
{"W-WW-NW+NWW", 8},
{"W+N-NW", 9},
{"W-NW", 10},
{"NW-N", 11},
{"N-NE", 12},
{"N-NN", 13},
{"W-WW", 14},
{"WGH", 15},
{"PrevAbs", 16},
{"Prev", 17},
{"PrevAbsErr", 18},
{"PrevErr", 19},
{"PPrevAbs", 20},
{"PPrev", 21},
{"PPrevAbsErr", 22},
{"PPrevErr", 23},
{"Prev1Abs", 16},
{"Prev1", 17},
{"Prev1AbsErr", 18},
{"Prev1Err", 19},
};
for (size_t i = 0; i < 19; i++) {
std::string name_prefix = "Prev" + std::to_string(i + 1);
property_map[name_prefix + "Abs"] = i * 4 + 16;
property_map[name_prefix] = i * 4 + 17;
property_map[name_prefix + "AbsErr"] = i * 4 + 18;
property_map[name_prefix + "Err"] = i * 4 + 19;
}
static const std::unordered_map<std::string, Predictor> predictor_map = {
{"Set", Predictor::Zero},
{"W", Predictor::Left},
{"N", Predictor::Top},
{"AvgW+N", Predictor::Average0},
{"Select", Predictor::Select},
{"Gradient", Predictor::Gradient},
{"Weighted", Predictor::Weighted},
{"NE", Predictor::TopRight},
{"NW", Predictor::TopLeft},
{"WW", Predictor::LeftLeft},
{"AvgW+NW", Predictor::Average1},
{"AvgN+NW", Predictor::Average2},
{"AvgN+NE", Predictor::Average3},
{"AvgAll", Predictor::Average4},
};
auto t = tok();
if (t == "if") {
// Decision node.
int p;
t = tok();
if (!property_map.count(t)) {
fprintf(stderr, "Unexpected property: %s\n", t.c_str());
return false;
}
p = property_map.at(t);
t = tok();
if (t != ">") {
fprintf(stderr, "Expected >, found %s\n", t.c_str());
return false;
}
t = tok();
size_t num = 0;
int split = std::stoi(t, &num);
if (num != t.size()) {
fprintf(stderr, "Invalid splitval: %s\n", t.c_str());
return false;
}
size_t pos = tree.size();
tree.emplace_back(PropertyDecisionNode::Split(p, split, pos + 1));
JXL_RETURN_IF_ERROR(ParseNode(tok, tree, spline_data, cparams, W, H, io,
have_next, x0, y0, buffer_size));
tree[pos].rchild = tree.size();
} else if (t == "-") {
// Leaf
t = tok();
Predictor p;
if (!predictor_map.count(t)) {
fprintf(stderr, "Unexpected predictor: %s\n", t.c_str());
return false;
}
p = predictor_map.at(t);
t = tok();
bool subtract = false;
if (t == "-") {
subtract = true;
t = tok();
} else if (t == "+") {
t = tok();
}
size_t num = 0;
int offset = std::stoi(t, &num);
if (num != t.size()) {
fprintf(stderr, "Invalid offset: %s\n", t.c_str());
return false;
}
if (subtract) offset = -offset;
tree.emplace_back(PropertyDecisionNode::Leaf(p, offset));
return true;
} else if (t == "Width") {
t = tok();
size_t num = 0;
W = std::stoul(t, &num);
if (num != t.size()) {
fprintf(stderr, "Invalid width: %s\n", t.c_str());
return false;
}
} else if (t == "Height") {
t = tok();
size_t num = 0;
H = std::stoul(t, &num);
if (num != t.size()) {
fprintf(stderr, "Invalid height: %s\n", t.c_str());
return false;
}
} else if (t == "/*") {
t = tok();
while (t != "*/" && t != "") t = tok();
} else if (t == "Squeeze") {
cparams.responsive = true;
} else if (t == "GroupShift") {
t = tok();
size_t num = 0;
cparams.modular_group_size_shift = std::stoul(t, &num);
if (num != t.size()) {
fprintf(stderr, "Invalid GroupShift: %s\n", t.c_str());
return false;
}
} else if (t == "XYB") {
cparams.color_transform = ColorTransform::kXYB;
} else if (t == "CbYCr") {
cparams.color_transform = ColorTransform::kYCbCr;
} else if (t == "HiddenChannel") {
t = tok();
size_t num = 0;
cparams.move_to_front_from_channel = -1 - std::stoul(t, &num);
if (num != t.size() || num > 16) {
fprintf(stderr, "Invalid HiddenChannel (max 16): %s\n", t.c_str());
return false;
}
} else if (t == "RCT") {
t = tok();
size_t num = 0;
cparams.colorspace = std::stoul(t, &num);
if (num != t.size()) {
fprintf(stderr, "Invalid RCT: %s\n", t.c_str());
return false;
}
} else if (t == "Orientation") {
t = tok();
size_t num = 0;
io.metadata.m.orientation = std::stoul(t, &num);
if (num != t.size()) {
fprintf(stderr, "Invalid Orientation: %s\n", t.c_str());
return false;
}
} else if (t == "Alpha") {
io.metadata.m.SetAlphaBits(io.metadata.m.bit_depth.bits_per_sample);
JXL_ASSIGN_OR_RETURN(
ImageF alpha, ImageF::Create(jpegxl::tools::NoMemoryManager(), W, H));
if (!io.frames[0].SetAlpha(std::move(alpha))) {
fprintf(stderr, "Internal: SetAlpha failed\n");
return false;
}
} else if (t == "Bitdepth") {
t = tok();
size_t num = 0;
uint32_t bits_per_sample = std::stoul(t, &num);
if (num != t.size() || bits_per_sample < 1 || bits_per_sample > 32) {
fprintf(stderr, "Invalid Bitdepth: %s\n", t.c_str());
return false;
}
if (buffer_size == 0) {
// Match the main encoder and use 32bit buffers for bitdepths over 12.
buffer_size = bits_per_sample > 12 ? 2 : 3;
}
io.metadata.m.bit_depth.bits_per_sample = bits_per_sample;
} else if (t == "FloatExpBits") {
t = tok();
size_t num = 0;
io.metadata.m.bit_depth.floating_point_sample = true;
io.metadata.m.bit_depth.exponent_bits_per_sample = std::stoul(t, &num);
if (num != t.size()) {
fprintf(stderr, "Invalid FloatExpBits: %s\n", t.c_str());
return false;
}
} else if (t == "FramePos") {
t = tok();
size_t num = 0;
x0 = std::stoi(t, &num);
if (num != t.size()) {
fprintf(stderr, "Invalid FramePos x0: %s\n", t.c_str());
return false;
}
t = tok();
y0 = std::stoi(t, &num);
if (num != t.size()) {
fprintf(stderr, "Invalid FramePos y0: %s\n", t.c_str());
return false;
}
} else if (t == "NotLast") {
have_next = JXL_TRUE;
} else if (t == "Upsample") {
t = tok();
size_t num = 0;
cparams.resampling = std::stoul(t, &num);
if (num != t.size() ||
(cparams.resampling != 1 && cparams.resampling != 2 &&
cparams.resampling != 4 && cparams.resampling != 8)) {
fprintf(stderr, "Invalid Upsample: %s\n", t.c_str());
return false;
}
} else if (t == "Upsample_EC") {
t = tok();
size_t num = 0;
cparams.ec_resampling = std::stoul(t, &num);
if (num != t.size() ||
(cparams.ec_resampling != 1 && cparams.ec_resampling != 2 &&
cparams.ec_resampling != 4 && cparams.ec_resampling != 8)) {
fprintf(stderr, "Invalid Upsample_EC: %s\n", t.c_str());
return false;
}
} else if (t == "Animation") {
io.metadata.m.have_animation = true;
io.metadata.m.animation.tps_numerator = 1000;
io.metadata.m.animation.tps_denominator = 1;
io.frames[0].duration = 100;
} else if (t == "AnimationFPS") {
t = tok();
size_t num = 0;
io.metadata.m.animation.tps_numerator = std::stoul(t, &num);
if (num != t.size()) {
fprintf(stderr, "Invalid numerator: %s\n", t.c_str());
return false;
}
t = tok();
num = 0;
io.metadata.m.animation.tps_denominator = std::stoul(t, &num);
if (num != t.size()) {
fprintf(stderr, "Invalid denominator: %s\n", t.c_str());
return false;
}
} else if (t == "Duration") {
t = tok();
size_t num = 0;
io.frames[0].duration = std::stoul(t, &num);
if (num != t.size()) {
fprintf(stderr, "Invalid Duration: %s\n", t.c_str());
return false;
}
} else if (t == "BlendMode") {
t = tok();
if (t == "kAdd") {
io.frames[0].blendmode = BlendMode::kAdd;
} else if (t == "kReplace") {
io.frames[0].blendmode = BlendMode::kReplace;
} else if (t == "kBlend") {
io.frames[0].blendmode = BlendMode::kBlend;
} else if (t == "kAlphaWeightedAdd") {
io.frames[0].blendmode = BlendMode::kAlphaWeightedAdd;
} else if (t == "kMul") {
io.frames[0].blendmode = BlendMode::kMul;
} else {
fprintf(stderr, "Invalid BlendMode: %s\n", t.c_str());
return false;
}
} else if (t == "SplineQuantizationAdjustment") {
t = tok();
size_t num = 0;
spline_data.quantization_adjustment = std::stoul(t, &num);
if (num != t.size()) {
fprintf(stderr, "Invalid SplineQuantizationAdjustment: %s\n", t.c_str());
return false;
}
} else if (t == "Spline") {
Spline spline;
const auto ParseFloat = [&t, &tok](float& output) {
t = tok();
size_t num = 0;
output = std::stof(t, &num);
if (num != t.size()) {
fprintf(stderr, "Invalid spline data: %s\n", t.c_str());
return false;
}
return true;
};
for (auto& dct : spline.color_dct) {
for (float& coefficient : dct) {
JXL_RETURN_IF_ERROR(ParseFloat(coefficient));
}
}
for (float& coefficient : spline.sigma_dct) {
JXL_RETURN_IF_ERROR(ParseFloat(coefficient));
}
while (true) {
t = tok();
if (t == "EndSpline") break;
size_t num = 0;
Spline::Point point;
point.x = std::stof(t, &num);
bool ok_x = num == t.size();
auto t_y = tok();
point.y = std::stof(t_y, &num);
if (!ok_x || num != t_y.size()) {
fprintf(stderr, "Invalid spline control point: %s %s\n", t.c_str(),
t_y.c_str());
return false;
}
spline.control_points.push_back(point);
}
if (spline.control_points.empty()) {
fprintf(stderr, "Spline with no control point\n");
return false;
}
spline_data.splines.push_back(std::move(spline));
} else if (t == "Gaborish") {
cparams.gaborish = jxl::Override::kOn;
} else if (t == "DeltaPalette") {
cparams.lossy_palette = true;
cparams.palette_colors = 0;
} else if (t == "EPF") {
t = tok();
size_t num = 0;
cparams.epf = std::stoul(t, &num);
if (num != t.size() || cparams.epf > 3) {
fprintf(stderr, "Invalid EPF: %s\n", t.c_str());
return false;
}
} else if (t == "Noise") {
cparams.manual_noise.resize(8);
for (size_t i = 0; i < 8; i++) {
t = tok();
size_t num = 0;
float v = std::stof(t, &num);
if (num != t.size() || v < 0.0f || v > 1.0f) {
fprintf(stderr, "Invalid noise entry: %s\n", t.c_str());
return false;
}
cparams.manual_noise[i] = jxl::Clamp1(v, 0.0f, jxl::kNoiseLutMax);
}
} else if (t == "XYBFactors") {
cparams.manual_xyb_factors.resize(3);
for (size_t i = 0; i < 3; i++) {
t = tok();
size_t num = 0;
cparams.manual_xyb_factors[i] = std::stof(t, &num);
if (num != t.size()) {
fprintf(stderr, "Invalid XYB factor: %s\n", t.c_str());
return false;
}
}
} else if (t == "PQ") {
io.metadata.m.color_encoding.Tf().transfer_function =
jxl::TransferFunction::kPQ;
io.metadata.m.tone_mapping.intensity_target = 10000;
} else if (t == "HLG") {
io.metadata.m.color_encoding.Tf().transfer_function =
jxl::TransferFunction::kHLG;
io.metadata.m.tone_mapping.intensity_target = 1000;
} else if (t == "Rec2100") {
JXL_RETURN_IF_ERROR(
io.metadata.m.color_encoding.SetPrimariesType(jxl::Primaries::k2100));
} else if (t == "P3") {
JXL_RETURN_IF_ERROR(
io.metadata.m.color_encoding.SetPrimariesType(jxl::Primaries::kP3));
} else if (t == "16BitBuffers") {
buffer_size = 1;
} else if (t == "32BitBuffers") {
buffer_size = 2;
} else {
fprintf(stderr, "Unexpected node type: %s\n", t.c_str());
return false;
}
JXL_RETURN_IF_ERROR(
ParseNode(tok, tree, spline_data, cparams, W, H, io, have_next, x0, y0,
buffer_size));
return true;
}
} // namespace
::jxl::Status JxlFromTree(const char* in, const char* out,
const char* tree_out) {
Tree tree;
SplineData spline_data;
CompressParams cparams = {};
size_t width = 1024;
size_t height = 1024;
int x0 = 0;
int y0 = 0;
cparams.SetLossless();
cparams.responsive = JXL_FALSE;
cparams.resampling = 1;
cparams.ec_resampling = 1;
cparams.modular_group_size_shift = 3;
cparams.colorspace = 0;
cparams.speed_tier = jxl::SpeedTier::kGlacier;
cparams.buffering = 0;
JxlMemoryManager* memory_manager = jpegxl::tools::NoMemoryManager();
auto io = jxl::make_unique<CodecInOut>(memory_manager);
io->metadata.m.modular_16_bit_buffer_sufficient = false;
int have_next = JXL_FALSE;
int buffer_size = 0;
std::istream* f = &std::cin;
std::ifstream file;
if (strcmp(in, "-") > 0) {
file.open(in, std::ifstream::in);
f = &file;
}
auto tok = [&f]() {
std::string out;
*f >> out;
return out;
};
if (!ParseNode(tok, tree, spline_data, cparams, width, height, *io, have_next,
x0, y0, buffer_size)) {
return JXL_FAILURE("Failed to ParseNode");
}
// Auto 16bit for multi-frame art would mean parsing all frames first,
// so for now just default to 32bit buffers instead.
if (buffer_size == 1 || (buffer_size == 3 && !have_next)) {
io->metadata.m.modular_16_bit_buffer_sufficient = true;
}
if (tree_out) {
PrintTree(tree, tree_out);
}
JXL_ASSIGN_OR_RETURN(Image3F image,
Image3F::Create(memory_manager, width, height));
JXL_RETURN_IF_ERROR(
io->SetFromImage(std::move(image), io->metadata.m.color_encoding));
JXL_RETURN_IF_ERROR(io->SetSize((width + x0), (height + y0)));
io->metadata.m.color_encoding.DecideIfWantICC(*JxlGetDefaultCms());
cparams.options.zero_tokens = true;
cparams.palette_colors = 0;
cparams.channel_colors_pre_transform_percent = 0;
cparams.channel_colors_percent = 0;
cparams.patches = jxl::Override::kOff;
cparams.already_downsampled = true;
cparams.custom_fixed_tree = tree;
std::vector<QuantizedSpline> quantized_splines;
std::vector<Spline::Point> starting_points;
JXL_RETURN_IF_ERROR(
SplinesFromSplineData(spline_data, quantized_splines, starting_points));
cparams.custom_splines = {Span<const QuantizedSpline>(quantized_splines),
Span<const Spline::Point>(starting_points)};
PaddedBytes compressed{memory_manager};
JXL_RETURN_IF_ERROR(io->CheckMetadata());
BitWriter writer{memory_manager};
std::unique_ptr<CodecMetadata> metadata = jxl::make_unique<CodecMetadata>();
*metadata = io->metadata;
JXL_RETURN_IF_ERROR(metadata->size.Set(io->xsize() * cparams.resampling,
io->ysize() * cparams.resampling));
metadata->m.xyb_encoded = (cparams.color_transform == ColorTransform::kXYB);
if (cparams.move_to_front_from_channel < -1) {
size_t nch = -1 - cparams.move_to_front_from_channel;
cparams.move_to_front_from_channel = 3 + metadata->m.num_extra_channels;
metadata->m.num_extra_channels += nch;
for (size_t _ = 0; _ < nch; _++) {
metadata->m.extra_channel_info.emplace_back();
auto& eci = metadata->m.extra_channel_info.back();
eci.type = jxl::ExtraChannel::kOptional;
JXL_ASSIGN_OR_RETURN(
ImageF ch, ImageF::Create(memory_manager, io->xsize(), io->ysize()));
io->frames[0].extra_channels().emplace_back(std::move(ch));
}
}
JXL_RETURN_IF_ERROR(WriteCodestreamHeaders(metadata.get(), &writer, nullptr));
writer.ZeroPadToByte();
while (true) {
FrameInfo info;
info.is_last = !FROM_JXL_BOOL(have_next);
if (!info.is_last) info.save_as_reference = 1;
io->frames[0].origin.x0 = x0;
io->frames[0].origin.y0 = y0;
info.clamp = false;
JXL_RETURN_IF_ERROR(jxl::EncodeFrame(
memory_manager, cparams, info, metadata.get(), io->frames[0],
*JxlGetDefaultCms(), nullptr, &writer, nullptr));
if (!have_next) break;
tree.clear();
spline_data.splines.clear();
have_next = JXL_FALSE;
cparams.manual_noise.clear();
if (!ParseNode(tok, tree, spline_data, cparams, width, height, *io,
have_next, x0, y0, buffer_size)) {
return JXL_FAILURE("Failed to ParseNode");
}
cparams.custom_fixed_tree = tree;
JXL_ASSIGN_OR_RETURN(Image3F image,
Image3F::Create(memory_manager, width, height));
JXL_RETURN_IF_ERROR(
io->SetFromImage(std::move(image), ColorEncoding::SRGB()));
io->frames[0].blend = true;
}
compressed = std::move(writer).TakeBytes();
if (!WriteFile(out, compressed)) {
fprintf(stderr, "Failed to write to \"%s\"\n", out);
return JXL_FAILURE("Failed to write output");
}
return true;
}
} // namespace tools
} // namespace jpegxl
int main(int argc, char** argv) {
if ((argc != 3 && argc != 4) ||
((strcmp(argv[1], "-") > 0) && !strcmp(argv[1], argv[2]))) {
fprintf(stderr, "Usage: %s tree_in.txt out.jxl [tree_drawing]\n", argv[0]);
return 1;
}
jxl::Status result = jpegxl::tools::JxlFromTree(argv[1], argv[2],
argc < 4 ? nullptr : argv[3]);
if (!result) {
fprintf(stderr, "FAILURE\n");
return EXIT_FAILURE;
}
return EXIT_SUCCESS;
}