// 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 "iterm2.hpp"
#include "chunk.hpp"
#include "dimensions.hpp"
#include "image.hpp"
#include "terminal.hpp"
#include "util.hpp"
#include
#include
#include
#include
#include
#include
#ifdef HAVE_STD_EXECUTION_H
# include
#else
# include
#endif
#include
namespace fs = std::filesystem;
Iterm2::Iterm2(std::unique_ptr new_image, std::mutex *stdout_mutex)
: image(std::move(new_image)),
stdout_mutex(stdout_mutex)
{
const auto dims = image->dimensions();
x = dims.x + 1;
y = dims.y + 1;
horizontal_cells = std::ceil(static_cast(image->width()) / dims.terminal->font_width);
vertical_cells = std::ceil(static_cast(image->height()) / dims.terminal->font_height);
}
Iterm2::~Iterm2()
{
const std::scoped_lock lock{*stdout_mutex};
util::clear_terminal_area(x, y, horizontal_cells, vertical_cells);
}
void Iterm2::draw()
{
str.append("\033]1337;File=inline=1;");
const auto filename = image->filename();
const auto num_bytes = fs::file_size(filename);
const auto encoded_filename =
util::base64_encode(reinterpret_cast(filename.c_str()), filename.size());
str.append(fmt::format("size={};name={};width={}px;height={}px:", num_bytes, encoded_filename, image->width(),
image->height()));
const int chunk_size = 1023;
const auto chunks = process_chunks(filename, chunk_size, num_bytes);
const int num_chunks = std::ceil(static_cast(num_bytes) / chunk_size);
const uint64_t bytes_per_chunk = 4 * ((chunk_size + 2) / 3) + 100;
str.reserve((num_chunks + 2) * bytes_per_chunk);
ranges::for_each(chunks, [this](const std::unique_ptr &chunk) { str.append(chunk->get_result()); });
str.append("\a");
const std::scoped_lock lock{*stdout_mutex};
util::save_cursor_position();
util::move_cursor(y, x);
std::cout << str << std::flush;
util::restore_cursor_position();
str.clear();
}
auto Iterm2::process_chunks(const std::string &filename, int chunk_size, size_t num_bytes)
-> std::vector>
{
const int num_chunks = std::ceil(static_cast(num_bytes) / chunk_size);
std::vector> chunks;
chunks.reserve(num_chunks + 2);
std::ifstream ifs(filename);
while (ifs.good()) {
auto chunk = std::make_unique(chunk_size);
ifs.read(chunk->get_buffer(), chunk_size);
chunk->set_size(ifs.gcount());
chunks.push_back(std::move(chunk));
}
#ifdef HAVE_STD_EXECUTION_H
std::for_each(std::execution::par_unseq, chunks.begin(), chunks.end(), Iterm2Chunk::process_chunk);
#else
oneapi::tbb::parallel_for_each(chunks.begin(), chunks.end(), Iterm2Chunk());
#endif
return chunks;
}