ascii_art/renderer/
mod.rs

1mod data;
2use crate::{canvas::AsciiCanvasItem, AsciiArt, AsciiCanvas, Result};
3pub use data::{AsciiData, AsciiSet};
4use fontdue::Font;
5use image::{imageops::FilterType, io::Reader, DynamicImage, GenericImageView, Pixel};
6use std::{io::Cursor, path::Path};
7
8#[derive(Copy, Clone, Debug)]
9pub enum AsciiColorMode {
10    Gray = 0,
11    Color = 1,
12    Mask = 2,
13}
14
15impl Default for AsciiColorMode {
16    fn default() -> Self {
17        Self::Gray
18    }
19}
20
21impl AsciiArt {
22    pub fn build_font_cache(&mut self, font: &Font, chars: &str) {
23        self.char_set.load_string(font, chars)
24    }
25}
26
27impl AsciiArt {
28    pub fn render_path(&self, path: impl AsRef<Path>) -> Result<AsciiCanvas> {
29        let img = Reader::open(path)?.decode()?;
30        Ok(self.render(img))
31    }
32    pub fn render_bytes(&self, bytes: &[u8]) -> Result<AsciiCanvas> {
33        let img = Reader::new(Cursor::new(bytes)).decode()?;
34        Ok(self.render(img))
35    }
36
37    pub fn render(&self, img: DynamicImage) -> AsciiCanvas {
38        unsafe {
39            match self.pixel_aligned {
40                true => self.render_grid(img),
41                false => self.render_mono(),
42            }
43        }
44    }
45    unsafe fn render_grid(&self, img: DynamicImage) -> AsciiCanvas {
46        let w = img.width() as f32 / self.font_size;
47        let h = img.height() as f32 / self.font_size;
48        let color_map = img.resize_exact(w.floor() as u32, h.floor() as u32, FilterType::CatmullRom).into_rgb();
49        let mut items = vec![];
50        for (x, y, rgb) in color_map.enumerate_pixels() {
51            let gray = rgb.to_luma();
52            let data = match self.reverse_color {
53                true => self.char_set.nearest(*gray.0.get_unchecked(0)),
54                false => self.char_set.nearest(255 - *gray.0.get_unchecked(0)),
55            };
56            items.push(AsciiCanvasItem {
57                x: self.font_size * (x as f32 + 1.0),
58                y: self.font_size * (y as f32 + 1.0),
59                color: rgb.to_owned(),
60                data,
61            })
62        }
63        AsciiCanvas {
64            data: items,
65            font_size: self.font_size,
66            width: w.ceil() * self.font_size,
67            height: h.ceil() * self.font_size,
68        }
69    }
70    fn render_mono(&self) -> AsciiCanvas {
71        unimplemented!()
72    }
73}