blob: 2c011182efc177521d8caa28887f33e544b9e813 [file] [log] [blame]
// Copyright 2020 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// -----------------------------------------------------------------------------
//
// simple command line tools to create/manipulate/optimize preview bits
//
// Author: Skal (pascal.massimino@gmail.com)
#include <cstdio>
#include <cstring>
#include <string>
#include <vector>
#include "examples/example_utils.h"
#include "extras/extras.h"
#include "imageio/file_format.h"
#include "imageio/image_dec.h"
#include "imageio/image_enc.h"
#include "imageio/imageio_util.h"
#include "src/common/preview/preview.h" // TODO(skal): make better include
#include "src/common/progress_watcher.h"
#include "src/dec/preview/preview_dec.h"
#include "src/enc/preview/preview_enc.h"
#include "src/wp2/encode.h"
//------------------------------------------------------------------------------
namespace {
static int verbose = 1;
static bool check = false;
} // namespace
#define VERBOSE(STR, ...) \
if (verbose > 0) printf((STR), __VA_ARGS__) // NOLINT (string literal)
//------------------------------------------------------------------------------
namespace WP2 {
namespace {
bool operator==(const AYCoCg19b& a, const AYCoCg19b& b) {
return (a.y == b.y) && (a.co == b.co) && (a.cg == b.cg) && (a.a == b.a);
}
bool operator==(const VertexIndexedColor& a, const VertexIndexedColor& b) {
return (a.x == b.x) && (a.y == b.y) && (a.color_index == b.color_index);
}
template <typename T>
bool SameArray(const T a[], const T b[], uint32_t size) {
for (uint32_t i = 0; i < size; ++i) {
if (!(a[i] == b[i])) return false;
}
return true;
}
bool operator==(const PreviewData& A, const PreviewData& B) {
return (A.grid_width_ == B.grid_width_) &&
(A.grid_height_ == B.grid_height_) &&
(A.palette_.size() == B.palette_.size()) &&
(A.vertices_.size() == B.vertices_.size()) &&
SameArray(A.corners_, B.corners_, 4) &&
SameArray(A.palette_.data(), B.palette_.data(), A.palette_.size()) &&
SameArray(A.vertices_.data(), B.vertices_.data(), A.vertices_.size());
}
//------------------------------------------------------------------------------
void Help() {
ProgramOptions opt;
opt.Add("Usage:");
opt.Add(" preview -convert [-dry][-noforce][-b64] text_files... ");
opt.Add(" preview image [bits|text] [options... ]"
"[-o output] [-d output]");
opt.Add("");
opt.Add("PreviewConfiguration Options:");
opt.Add(" -m [edge,rand,diff,voronoi]", "analysis method");
opt.Add(" -n <int>", "starting number of vertices");
opt.Add(" -nc <int>", "starting number of colors");
opt.Add(" -density <float>", "grid density");
opt.Add(" -g <int>", "initial grid size");
opt.Add(" -b <int>", "blur radius");
opt.Add("");
opt.Add("Optimization Options:");
opt.Add(" -s <int>", "target size in bytes");
opt.Add(" -i <int>", "number of optim iterations");
opt.Add(" -l <int>", "size vs quality trade-off. "
"0=favor quality, 100=favor size");
opt.Add(" -p none", "reset all proba to 0");
opt.Add(" -p <move> <int>", "change the proba for the given <move>:");
opt.Add(" . vmove: vertex move");
opt.Add(" . vadd: vertex insertion");
opt.Add(" . vsub: vertex removal");
opt.Add(" . cmove: color move");
opt.Add(" . cadd: color insertion");
opt.Add(" . csub: color removal");
opt.Add(" . cidx: color index move");
opt.Add("");
opt.Add("Other Options:");
opt.Add(" -txt", "use Text for preview data");
opt.Add(" -info", "print full preview info");
opt.Add(" -short", "print short preview info");
opt.Add(" -b64", "print base64 output");
opt.Add(" -w <int>", "output width for thumbnail");
opt.Add(" -d <file_prefix>", "prefix for reconstructed thumbnail");
opt.Add(" -o <file_prefix>", "prefix for output data");
opt.Add(" -check", "perform round-trip check");
opt.Add(" -noforce", "don't force file over-write");
opt.Add(" -quiet", "reduced verbosity");
opt.Add(" -verbose", "extra verbosity");
opt.Print();
}
//------------------------------------------------------------------------------
WP2_NO_DISCARD WP2Status ConvertTextFiles(int argc, const char* argv[]) {
// pure txt->bits conversion mode
uint32_t total_size = 0;
uint32_t nb_files = 0;
bool dry_run = false;
bool overwrite = true;
bool to_base64 = false;
for (int c = 2; c < argc; ++c) {
if (!strcmp(argv[c], "-dry")) {
dry_run = true;
continue;
} else if (!strcmp(argv[c], "-noforce")) {
overwrite = false;
continue;
} else if (!strcmp(argv[c], "-b64")) {
to_base64 = true;
continue;
} else if (!strcmp(argv[c], "-h")) {
Help();
return WP2_STATUS_OK;
}
std::string in;
CHECK_STATUS(IoUtilReadFile(argv[c], &in), "bad input [%s]\n", argv[c]);
PreviewData data;
CHECK_STATUS(PreviewFromText(in, &data), "Error in text-to-preview\n");
ANSEnc enc;
CHECK_STATUS(data.Encode(&enc),
"Error encoding to binary! SHOULDN'T HAPPEN!!\n");
if (to_base64) {
const std::string out = PreviewToBase64(data);
CHECK_TRUE(!out.empty(), "Error during Base64 generation!\n");
printf("### argv[c] ###\n%s\n", out.c_str());
} else if (!dry_run) {
const std::string out_name = RemoveFileExtension(argv[c]) + ".bits";
Vector_u8 out;
CHECK_STATUS(enc.WriteBitstreamTo(out), "WriteBitstreamTo() failed.");
CHECK_STATUS(
IoUtilWriteFile(out.data(), out.size(), out_name.c_str(), overwrite),
"Couldn't save compressed bits [%s]!\n", out_name.c_str());
}
if (check) {
PreviewData verif;
Vector_u8 out;
CHECK_STATUS(enc.WriteBitstreamTo(out), "WriteBitstreamTo() failed.");
CHECK_STATUS(verif.Decode(out.data(), out.size()),
"Round-trip Decode() failed.\n");
CHECK_TRUE(verif == data, "Round-trip verification failed!\n");
}
total_size += enc.GetBitstreamSize();
++nb_files;
}
VERBOSE("%u files, size: %u bytes\n", nb_files, total_size);
return WP2_STATUS_OK;
}
//------------------------------------------------------------------------------
void ResetProbas(PreviewConfig* const config) {
config->proba_vertex_move = 0;
config->proba_vertex_add = 0;
config->proba_vertex_sub = 0;
config->proba_color_move = 0;
config->proba_color_add = 0;
config->proba_color_sub = 0;
config->proba_color_index_move = 0;
}
} // namespace
} // namespace WP2
//------------------------------------------------------------------------------
using WP2::ExUtilGetUInt;
using WP2::PreviewConfig;
int main(int argc, const char* argv[]) {
if (argc < 2) {
fprintf(stderr, "missing arguments!\n");
WP2::Help();
return 1;
}
if (argc > 1 && !strcmp(argv[1], "-convert")) {
return WP2::ConvertTextFiles(argc, argv);
}
WP2::PreviewConfig config(75.f, 5);
const char* img_file = nullptr;
const char* bin_file = nullptr;
const char* out_file = nullptr;
const char* preview_file = nullptr;
bool overwrite = true;
uint32_t out_w = 256; // width of decoded preview
enum { TEXT, INFO, SHORT, BASE64 } out_format = SHORT;
for (int c = 1; c < argc; ++c) {
bool parse_error = false;
if (!strcmp(argv[c], "-h")) {
WP2::Help();
return 0;
} else if (!strcmp(argv[c], "-check")) {
check = true;
} else if (!strcmp(argv[c], "-noforce")) {
overwrite = false;
} else if (!strcmp(argv[c], "-quiet")) {
verbose = 0;
out_format = SHORT;
} else if (!strcmp(argv[c], "-verbose")) {
verbose = 2;
} else if (!strcmp(argv[c], "-txt")) {
out_format = TEXT;
} else if (!strcmp(argv[c], "-info")) {
out_format = INFO;
} else if (!strcmp(argv[c], "-short")) {
out_format = SHORT;
} else if (!strcmp(argv[c], "-b64")) {
out_format = BASE64;
} else if (!strcmp(argv[c], "-w") && c + 1 < argc) {
out_w = std::max(ExUtilGetUInt(argv[++c], &parse_error), 32u);
} else if (!strcmp(argv[c], "-o") && c + 1 < argc) {
out_file = argv[++c];
} else if (!strcmp(argv[c], "-d") && c + 1 < argc) {
preview_file = argv[++c];
} else if (!strcmp(argv[c], "-s") && c + 1 < argc) {
config.target_num_bytes = ExUtilGetUInt(argv[++c], &parse_error);
} else if (!strcmp(argv[c], "-l") && c + 1 < argc) {
config.importance_of_size_over_quality =
4.4f * ExUtilGetUInt(argv[++c], &parse_error) / 100.f;
} else if (!strcmp(argv[c], "-p") && c + 1 < argc) {
if (!strcmp(argv[c + 1], "none")) {
WP2::ResetProbas(&config);
c += 1;
} else if (c + 2 < argc) {
auto* const dst =
!strcmp(argv[c + 1], "vmove") ? &config.proba_vertex_move :
!strcmp(argv[c + 1], "vadd") ? &config.proba_vertex_add :
!strcmp(argv[c + 1], "vsub") ? &config.proba_vertex_sub :
!strcmp(argv[c + 1], "cmove") ? &config.proba_color_move :
!strcmp(argv[c + 1], "cadd") ? &config.proba_color_add :
!strcmp(argv[c + 1], "csub") ? &config.proba_color_sub :
!strcmp(argv[c + 1], "cidx") ? &config.proba_color_index_move :
nullptr;
CHECK_TRUE(dst != nullptr, "Invalid -p move '%s'\n", argv[c + 1]);
*dst = ExUtilGetUInt(argv[c + 2], &parse_error);
c += 2;
}
} else if (!strcmp(argv[c], "-m") && c + 1 < argc) {
++c;
if (!strcmp(argv[c], "edge")) {
config.analysis_method =
PreviewConfig::AnalysisMethod::kEdgeSelectionAndRepulsion;
} else if (!strcmp(argv[c], "diff")) {
config.analysis_method =
PreviewConfig::AnalysisMethod::kColorDiffMaximization;
} else if (!strcmp(argv[c], "rand")) {
config.analysis_method =
PreviewConfig::AnalysisMethod::kRandomEdgeSelection;
} else if (!strcmp(argv[c], "voronoi")) {
config.analysis_method = PreviewConfig::AnalysisMethod::kVoronoi;
} else {
CHECK_TRUE(false, "Unknown analysis method '%s'\n", argv[c]);
}
} else if (!strcmp(argv[c], "-n") && c + 1 < argc) {
config.num_vertices = ExUtilGetUInt(argv[++c], &parse_error);
} else if (!strcmp(argv[c], "-nc") && c + 1 < argc) {
config.num_colors = ExUtilGetUInt(argv[++c], &parse_error);
} else if (!strcmp(argv[c], "-density") && c + 1 < argc) {
config.grid_density = ExUtilGetUInt(argv[++c], &parse_error);
} else if (!strcmp(argv[c], "-g") && c + 1 < argc) {
config.grid_size = ExUtilGetUInt(argv[++c], &parse_error);
} else if (!strcmp(argv[c], "-b") && c + 1 < argc) {
config.blur_radius = ExUtilGetUInt(argv[++c], &parse_error);
} else if (!strcmp(argv[c], "-i") && c + 1 < argc) {
config.num_iterations = ExUtilGetUInt(argv[++c], &parse_error);
} else if (c < argc && argv[c][0] != '-') {
if (img_file == nullptr) {
img_file = argv[c];
} else {
bin_file = argv[c];
}
} else {
parse_error = true;
}
if (parse_error) {
WP2::Help();
return 1;
}
}
CHECK_TRUE(img_file != nullptr, "Missing input image");
CHECK_TRUE(config.IsValid(), "PreviewConfig is invalid.\n");
WP2::ArgbBuffer original;
CHECK_STATUS(WP2::ReadImage(img_file, &original),
"Can't read image [%s]\n", img_file);
WP2::PreviewData data;
if (bin_file == nullptr) {
WP2::MemoryWriter writer;
CHECK_STATUS(
WP2::EncodePreview(original, config, WP2::ProgressRange(), &writer),
"Error in EncodePreview()\n");
CHECK_STATUS(data.Decode(writer.mem_, writer.size_),
"Round-trip error. SHOULDN'T HAPPEN!!'\n");
} else {
// Read the starting state
const bool as_txt = (WP2::GetFileExtension(bin_file) == "txt");
std::string bin;
CHECK_STATUS(WP2::IoUtilReadFile(bin_file, &bin),
"bad bin file [%s]\n", bin_file);
if (as_txt) {
CHECK_STATUS(PreviewFromText(bin, &data), "Error in text-to-preview\n");
} else {
CHECK_STATUS(data.Decode((const uint8_t*)bin.data(), bin.size()),
"Can't decode binary data [%s]", bin_file);
}
// optimize further
CHECK_STATUS(
data.Optimize(original, config, WP2::ProgressRange(), /*log=*/true),
"Optimize() call failed\n");
}
// save binary/text result
WP2::ANSEnc enc;
CHECK_STATUS(data.Encode(&enc),
"Error encoding to binary! SHOULDN'T HAPPEN!!\n");
WP2::Vector_u8 bits;
CHECK_STATUS(enc.WriteBitstreamTo(bits), "WriteBitstreamTo() failed.");
const uint32_t preview_size = bits.size();
const uint8_t* const preview_data = bits.data();
VERBOSE("Creating preview for %s. Size:%u\n", img_file, preview_size);
if (out_file != nullptr) {
const bool as_txt = (WP2::GetFileExtension(out_file) == "txt");
if (as_txt) {
const std::string s = PreviewToText(data);
CHECK_STATUS(
WP2::IoUtilWriteFile(
(const uint8_t*)s.data(), s.size(), out_file, overwrite),
"Couldn't write preview as text [%s]!\n", out_file);
} else { // raw output
CHECK_STATUS(
WP2::IoUtilWriteFile(
preview_data, preview_size, out_file, overwrite),
"Couldn't write preview as binary [%s]!\n", out_file);
}
VERBOSE("Saved [%s] as %s\n", out_file, as_txt ? "text" : "binary");
}
// save reconstructed preview
if (preview_file != nullptr) {
const uint32_t out_h = std::max(
32u, WP2::DivRound(out_w * original.height(), original.width()));
WP2::ArgbBuffer thumb;
CHECK_STATUS(thumb.Resize(out_w, out_h), "Memory error\n");
CHECK_STATUS(WP2::DecodePreview(preview_data, preview_size, &thumb),
"Error in DecodePreview()\n");
CHECK_STATUS(WP2::SaveImage(thumb, preview_file, overwrite),
"Couldn't save decoded preview as [%s]!\n",
preview_file);
VERBOSE("Saved [%s]\n", preview_file);
}
// print final info
std::string infos = "\n";
if (out_format == INFO) {
infos = PrintPreview(data, /* reduced= */false);
} else if (out_format == SHORT) {
infos = PrintPreview(data, /* reduced= */true);
} else if (out_format == TEXT) {
infos = PreviewToText(data);
} else if (out_format == BASE64) {
infos = PreviewToBase64(data) + "\n";
}
VERBOSE("%s", infos.c_str());
return 0;
}