summaryrefslogtreecommitdiff
path: root/src/image.cpp
diff options
context:
space:
mode:
authorpants <niklasjo2004@gmail.com>2026-09-21 14:18:06 -0700
committerpants <niklasjo2004@gmail.com>2026-09-21 14:18:06 -0700
commit83d5ba42f8a98fc0df177efef4333f74a0481f61 (patch)
tree6f1fcff64ec9adb2fc51a7cfcc451eb22a103fb3 /src/image.cpp
Diffstat (limited to 'src/image.cpp')
-rw-r--r--src/image.cpp185
1 files changed, 185 insertions, 0 deletions
diff --git a/src/image.cpp b/src/image.cpp
new file mode 100644
index 0000000..fae3ef6
--- /dev/null
+++ b/src/image.cpp
@@ -0,0 +1,185 @@
+// 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 <https://www.gnu.org/licenses/>.
+
+#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 <opencv2/imgcodecs.hpp>
+#endif
+#include <spdlog/spdlog.h>
+#include <vips/vips.h>
+
+namespace fs = std::filesystem;
+using njson = nlohmann::json;
+
+auto Image::load(const njson &command, const Terminal *terminal) -> std::unique_ptr<Image>
+{
+ 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> 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<OpencvImage>(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<LibvipsImage>(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, int>
+{
+ 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<int>(img_width * new_scale);
+ new_height = static_cast<int>(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<int>(new_width * new_scale);
+ new_height = static_cast<int>(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<Dimensions>
+{
+ 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<Dimensions>(terminal, xcoord, ycoord, max_width, max_height, scaler);
+}