fdehau / tui-rs

Build terminal user interfaces and dashboards using Rust
MIT License
10.84k stars 484 forks source link

Question: How to render a picture? #114

Closed bytesnail closed 5 years ago

bytesnail commented 5 years ago

Just like a map, I want to render a picture in the terminal (the picture can be arbitrarily specified). What should I do? Is it possible to render the image as a generic feature?

bytesnail commented 5 years ago

I successfully rendered an image using the Points module:

_deepin-terminal_20190122023305

The key code snippets are as follows:

pub fn open<P>(path: P, width: u32, height: u32) -> RgbImage
where
    P: AsRef<Path>,
{
    let img = image::open(path).unwrap();
    img.resize_to_fill(width, height, FilterType::Gaussian)
        .to_rgb()
}

pub fn group_by_color(img: RgbImage) -> HashMap<(u8, u8, u8), Vec<(f64, f64)>> {
    let mut result = HashMap::<(u8, u8, u8), Vec<(f64, f64)>>::new();
    let (_, height) = img.dimensions();
    let height = height as i32;
    for (x, y, color) in img.enumerate_pixels() {
        let x = f64::from(x);
        let y = f64::from(height - 1 - (y as i32));
        let key = (color.data[0], color.data[1], color.data[2]);
        if let Some(origin_value) = result.get(&key) {
            let mut value = origin_value.clone();
            value.push((x, y));
            result.insert(key, value);
        } else {
            let mut value = Vec::<(f64, f64)>::new();
            value.push((x, y));
            result.insert(key, value);
        }
    }
    result
}
let chunks = Layout::default()
    .constraints([Constraint::Percentage(100)].as_ref())
    .split(f.size());

Canvas::default()
    .block(Block::default().borders(Borders::NONE))
    .x_bounds([0.0, 359.0])
    .y_bounds([0.0, 179.0])
    .paint(|ctx| {
        for color in img.keys() {
            if let Some(points) = img.get(&color) {
                ctx.draw(&Points {
                    coords: points,
                    color: Color::Rgb(color.0, color.1, color.2),
                })
            }
        }
    })
    .render(&mut f, chunks[0]);
bytesnail commented 5 years ago

There are two new questions:

fdehau commented 5 years ago

First, the Canvas is implemented by using the braille characters. The idea is that by default the terminal can be divided as a grid of cells with the width and height of your terminal with one "symbol" per cell. To get more "addressable" cells for the canvas' points, a cell is divided in 8 more sub-cells (2 by 4) where each of them can be filled with one "dot" of a braille symbol. There is a braille symbol for every combination of sub-cells.

Thus, as you have experimented yourself an image rendered using the points interface will result in a point cloud with a black background. To be honest I've not tried any image rendering so I would be interested if you could point me out to a full code example, including the image loading bit, so that I or others could experiment a bit with this =).

In any way, I'm afraid that you cannot achieve what you want with the current state of the crate and that some changes are required on the library side.I would maybe start by modifying the Shape trait to take a Style struct along the coordinates so that you can have more control over the colors of each cell.

bytesnail commented 5 years ago

@fdehau I have submitted a full example of image rendering. #141

fdehau commented 5 years ago

closing in favor of the more recent issue