// Display images inside a terminal
// Copyright (C) 2023 JustKidding
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
#include "image.hpp"
#ifdef ENABLE_OPENCV
# include "image/opencv.hpp"
#endif
#include "dimensions.hpp"
#include "flags.hpp"
#include "image/libvips.hpp"
#include "util.hpp"
#ifdef ENABLE_OPENCV
# include
#endif
#include
#include
namespace fs = std::filesystem;
using njson = nlohmann::json;
auto Image::load(const njson &command, const Terminal *terminal) -> std::unique_ptr
{
const fs::path &filename = command.at("path");
if (!fs::exists(filename)) {
return nullptr;
}
const auto flags = Flags::instance();
const auto logger = spdlog::get("main");
std::shared_ptr dimensions;
try {
dimensions = get_dimensions(command, terminal);
} catch (const std::exception &) {
logger->error("Could not parse dimensions from command");
return nullptr;
}
std::string image_path = filename;
bool in_cache = false;
if (!flags->no_cache) {
image_path = check_cache(*dimensions, filename);
in_cache = image_path != filename;
}
#ifdef ENABLE_OPENCV
if (cv::haveImageReader(image_path) && !flags->no_opencv) {
try {
return std::make_unique(dimensions, image_path, in_cache);
} catch (const std::runtime_error &) {
return nullptr;
}
}
#endif
const auto *vips_loader = vips_foreign_find_load(image_path.c_str());
if (vips_loader != nullptr) {
try {
return std::make_unique(dimensions, image_path, in_cache);
} catch (const vips::VError &) {
return nullptr;
}
}
return nullptr;
}
auto Image::check_cache(const Dimensions &dimensions, const fs::path &orig_path) -> std::string
{
const fs::path cache_path = util::get_cache_file_save_location(orig_path);
if (!fs::exists(cache_path)) {
return orig_path;
}
vips::VImage cache_img;
try {
cache_img = vips::VImage::new_from_file(cache_path.c_str());
} catch (const vips::VError &) {
return orig_path;
}
const uint32_t img_width = cache_img.width();
const uint32_t img_height = cache_img.height();
const uint32_t dim_width = dimensions.max_wpixels();
const uint32_t dim_height = dimensions.max_hpixels();
const int delta = 10;
if ((dim_width >= img_width && dim_height >= img_height) &&
((dim_width - img_width) <= delta || (dim_height - img_height) <= delta)) {
return cache_path;
}
return orig_path;
}
auto Image::get_new_sizes(double max_width, double max_height, std::string_view scaler, int scale_factor) const
-> std::pair
{
int img_width = width();
int img_height = height();
int new_width = img_width;
int new_height = img_height;
double new_scale = 0;
double width_scale = 0;
double height_scale = 0;
double min_scale = 0;
double max_scale = 0;
if (scaler == "fit_contain" || scaler == "forced_cover") {
// I believe these should work the same
new_scale = max_height / img_height;
if (img_width >= img_height) {
new_scale = max_width / img_width;
}
new_width = static_cast(img_width * new_scale);
new_height = static_cast(img_height * new_scale);
new_scale = 1;
}
if (new_height > max_height) {
if (new_width > max_width) {
width_scale = max_width / new_width;
height_scale = max_height / new_height;
min_scale = std::min(width_scale, height_scale);
max_scale = std::max(width_scale, height_scale);
if (new_width * max_scale <= max_width && new_height * max_scale <= max_height) {
new_scale = max_scale;
} else {
new_scale = min_scale;
}
} else {
new_scale = max_height / new_height;
}
} else if (new_width > max_width) {
new_scale = max_width / new_width;
}
if (new_scale != 1) {
new_width = static_cast(new_width * new_scale);
new_height = static_cast(new_height * new_scale);
}
return std::make_pair(util::round_up(new_width, scale_factor), util::round_up(new_height, scale_factor));
}
auto Image::get_dimensions(const njson &json, const Terminal *terminal) -> std::shared_ptr
{
using std::string;
int xcoord = 0;
int ycoord = 0;
int max_width = 0;
int max_height = 0;
string width_key = "max_width";
string height_key = "max_height";
const string scaler = json.value("scaler", "contain");
if (json.contains("width")) {
width_key = "width";
height_key = "height";
}
if (json.at(width_key).is_string()) {
const string &width = json.at(width_key);
const string &height = json.at(height_key);
max_width = std::stoi(width);
max_height = std::stoi(height);
} else {
max_width = json.at(width_key);
max_height = json.at(height_key);
}
if (json.at("x").is_string()) {
const string &xcoords = json.at("x");
const string &ycoords = json.at("y");
xcoord = std::stoi(xcoords);
ycoord = std::stoi(ycoords);
} else {
xcoord = json.at("x");
ycoord = json.at("y");
}
return std::make_shared(terminal, xcoord, ycoord, max_width, max_height, scaler);
}