termcanvas 0.1.0

A lightweight Rust library for rendering pixels directly in the terminal.
Documentation
#![allow(dead_code)]

pub mod draw;
pub mod font;

use crossterm::terminal;
use std::io::{Write, stdout};

pub struct Canvas {
    width: u32,
    height: u32,
    pixels: Vec<u32>,
    out: Vec<u8>,
}

impl Canvas {
    pub fn new() -> Self {
        let (w, h_half) = terminal::size().unwrap();
        let h = h_half * 2;
        let size = w as usize * h as usize;
        print!("\x1b[3J\x1b[H\x1b[?25l\x1b[?1049h");
        stdout().flush().unwrap();
        Self {
            width: w as u32,
            height: h as u32,
            pixels: vec![0; size],
            out: Vec::with_capacity(w as usize * h_half as usize * 25),
        }
    }

    pub fn set_pixel(&mut self, x: u32, y: u32, color: u32) {
        if x >= self.width || y >= self.height {
            return;
        }
        self.pixels[y as usize * self.width as usize + x as usize] = color;
    }

    pub fn get_pixel(&self, x: u32, y: u32) -> u32 {
        if x >= self.width || y >= self.height {
            return 0;
        }
        self.pixels[y as usize * self.width as usize + x as usize]
    }

    pub fn clear(&mut self, color: u32) {
        self.pixels.fill(color);
    }

    pub fn render(&mut self) {
        self.out.clear();
        self.out.extend_from_slice(b"\x1b[?2026h\x1b[H");

        let rows = self.height / 2;
        let mut last_fg = 0;
        let mut last_bg = 0;

        self.out
            .extend_from_slice(b"\x1b[38;2;0;0;0m\x1b[48;2;0;0;0m");

        for row in 0..rows {
            let inv = rows - 1 - row;
            let y_top = inv * 2 + 1;
            let y_bottom = inv * 2;

            for x in 0..self.width {
                let fg = self.get_pixel(x, y_top);
                let bg = self.get_pixel(x, y_bottom);

                if fg != last_fg {
                    write!(
                        &mut self.out,
                        "\x1b[38;2;{};{};{}m",
                        (fg >> 24) as u8,
                        (fg >> 16) as u8,
                        (fg >> 8) as u8
                    )
                    .unwrap();
                    last_fg = fg;
                }
                if bg != last_bg {
                    write!(
                        &mut self.out,
                        "\x1b[48;2;{};{};{}m",
                        (bg >> 24) as u8,
                        (bg >> 16) as u8,
                        (bg >> 8) as u8
                    )
                    .unwrap();
                    last_bg = bg;
                }

                self.out.extend_from_slice("".as_bytes());
            }
        }

        self.out.extend_from_slice(b"\x1b[0m\x1b[?2026l");
        let mut stdout = stdout();
        stdout.write_all(&self.out).unwrap();
        stdout.flush().unwrap();
    }

    pub fn resize(&mut self, new_width: u32, new_height: u32) {
        self.width = new_width;
        self.height = new_height;
        self.pixels.clear();
        self.pixels.resize((new_width * new_height) as usize, 0);
        self.out.clear();
        self.out
            .reserve(new_width as usize * (new_height / 2) as usize * 20);
    }

    pub fn width(&self) -> u32 {
        self.width
    }
    pub fn height(&self) -> u32 {
        self.height
    }

    pub fn end(self) {
        print!("\x1b[?25h\x1b[?1049l\x1b[2J\x1b[3J");
        stdout().flush().unwrap();
    }

    pub fn terminal_width() -> u32 {
        terminal::size().expect("terminal::size()").0 as u32
    }
    pub fn terminal_height() -> u32 {
        2 * terminal::size().expect("terminal::size()").1 as u32
    }
}