ukiyoe 0.0.4

A library for rendering images to the terminal
Documentation
use ansi_escapes::{CursorMove, CursorTo};
use colored::Colorize;
use image::DynamicImage;
use image::GenericImageView;
use image::Pixel;
use image::Rgba;

pub struct Image {
    pub path: String,
    image: Option<DynamicImage>,
    cached_image: Option<(u16, u16, Vec<String>)>,
}

impl Image {
    pub fn new(path: &str) -> Image {
        Image {
            path: path.to_owned(),
            image: None,
            cached_image: None,
        }
    }

    fn generate_cached_image(
        &self,
        width: u16,
        height: u16,
        image: &DynamicImage,
    ) -> (u16, u16, Vec<String>) {
        let pixels: Vec<_> = image.pixels().collect();
        let (image_width, image_height) = image.dimensions();
        return Image::transform_pixels_to_lines(width, height, &pixels, image_width, image_height);
    }

    fn transform_pixels_to_lines(
        width: u16,
        height: u16,
        pixels: &[(u32, u32, Rgba<u8>)],
        image_width: u32,
        image_height: u32,
    ) -> (u16, u16, Vec<String>) {
        let mut lines = Vec::new();

        for y in 0..height {
            let mut line = String::new();
            for x in 0..width {
                let r_x = (x as f32 / width as f32 * image_width as f32) as u32;
                let r_y = (y as f32 / height as f32 * image_height as f32) as u32;
                let r_half_y =
                    (r_y + ((y + 1) as f32 / height as f32 * image_height as f32) as u32) / 2;
                let top_pixel = pixels[(r_y * image_width + r_x) as usize];
                let bottom_pixel = pixels[(r_half_y * image_width + r_x) as usize];
                let top_color = top_pixel.2 .0;
                let bottom_color = bottom_pixel.2 .0;
                line.push_str(&format!(
                    "{}",
                    ""
                        .truecolor(bottom_color[0], bottom_color[1], bottom_color[2])
                        .on_truecolor(top_color[0], top_color[1], top_color[2])
                ));
            }
            lines.push(line);
        }

        (width, height, lines)
    }

    fn lazy_load(&mut self, width: u16, height: u16) {
        if let None = self.image {
            self.image = Some(image::open(&self.path).unwrap());
        }
        if let Some(im) = &self.image {
            if let None = self.cached_image {
                self.cached_image = Some(self.generate_cached_image(width, height, im));
            }
            if let Some(c) = self.cached_image.as_ref() {
                if c.0 != width && c.1 != height {
                    self.cached_image = Some(self.generate_cached_image(width, height, im));
                }
            }
        } else {
            panic!("No image");
        }
    }

    pub fn render_at_position(&mut self, x: u16, y: u16, width: u16, height: u16) {
        self.lazy_load(width, height);

        let image = self.cached_image.as_ref().expect("No cached image");
        print!("{}", CursorTo::AbsoluteXY(x, y));
        let char_rows = &image.2;
        for char_row in char_rows {
            print!("{}{}", char_row, CursorMove::XY(-(width as i16), 1))
        }
    }

    pub fn render(&mut self, width: u16, height: u16) {
        self.lazy_load(width, height);
        let image = self.cached_image.as_ref().expect("No cached image");
        let char_rows = &image.2;
        for char_row in char_rows {
            println!("{}", char_row);
        }
    }

    pub fn render_pixels_at_position(
        x: u16,
        y: u16,
        width: u16,
        height: u16,
        pixels: &[[u8; 4]],
        pixel_width: usize,
    ) {
        print!("{}", CursorTo::AbsoluteXY(x, y));
        let numbered_pixels: Vec<(u32, u32, Rgba<u8>)> = pixels
            .iter()
            .enumerate()
            .map(|(i, pixel)| {
                let x = i % pixel_width;
                let y = i / pixel_width;
                let color = Rgba::<u8>::from_slice(pixel);
                (x as u32, y as u32, color.clone())
            })
            .collect();
        let char_rows = Image::transform_pixels_to_lines(
            width,
            height,
            &numbered_pixels,
            pixel_width as u32,
            pixels.len() as u32 / pixel_width as u32,
        );
        let char_rows = char_rows.2;
        for char_row in char_rows {
            print!("{}{}", char_row, CursorMove::XY(-(width as i16), 1))
        }
    }

    pub fn render_pixels(width: u16, height: u16, pixels: &[[u8; 4]], pixel_width: usize) {
        let numbered_pixels: Vec<(u32, u32, Rgba<u8>)> = pixels
            .iter()
            .enumerate()
            .map(|(i, pixel)| {
                let x = i % pixel_width;
                let y = i / pixel_width;
                let color = Rgba::<u8>::from_slice(pixel);
                (x as u32, y as u32, color.clone())
            })
            .collect();
        let char_rows = Image::transform_pixels_to_lines(
            width,
            height,
            &numbered_pixels,
            pixel_width as u32,
            pixels.len() as u32 / pixel_width as u32,
        );
        let char_rows = char_rows.2;
        for char_row in char_rows {
            print!("{}{}", char_row, CursorMove::XY(-(width as i16), 1))
        }
    }
}