// 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 "sixel.hpp"
#include "dimensions.hpp"
#include "terminal.hpp"
#include "util.hpp"
#include
#include
namespace fs = std::filesystem;
Sixel::Sixel(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);
const auto draw_callback = [](char *data, int size, void *priv) -> int {
auto *str = static_cast(priv);
str->append(data, size);
return size;
};
sixel_output_new(&output, draw_callback, &str, nullptr);
const auto file_size = fs::file_size(image->filename());
constexpr auto reserve_ratio = 50;
str.reserve(file_size * reserve_ratio);
// create dither and palette from image
sixel_dither_new(&dither, -1, nullptr);
sixel_dither_initialize(dither, const_cast(image->data()), image->width(), image->height(),
SIXEL_PIXELFORMAT_RGB888, SIXEL_LARGE_LUM, SIXEL_REP_CENTER_BOX, SIXEL_QUALITY_HIGH);
}
Sixel::~Sixel()
{
can_draw.store(false);
if (draw_thread.joinable()) {
draw_thread.join();
}
sixel_dither_destroy(dither);
sixel_output_destroy(output);
const std::scoped_lock lock{*stdout_mutex};
util::clear_terminal_area(x, y, horizontal_cells, vertical_cells);
}
void Sixel::draw()
{
if (!image->is_animated()) {
generate_frame();
return;
}
// start drawing loop
draw_thread = std::thread([this] {
while (can_draw.load()) {
generate_frame();
image->next_frame();
std::this_thread::sleep_for(std::chrono::milliseconds(image->frame_delay()));
}
});
}
void Sixel::generate_frame()
{
// output sixel content to stream
sixel_encode(const_cast(image->data()), image->width(), image->height(), 3 /*unused*/, dither,
output);
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();
}