ascii_izer/
lib.rs

1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3//! > **An image to ASCII art library for rust**
4//! ## Features
5//! * Color \[default\]: Processes the image color alongside the pixels during
6//!   ASCII generation.
7//! * Crossterm: Provides a simple method to put the ASCII into the console via
8//!   crossterm. This does not output color
9//!
10//! ## Usage
11//! ### Simple
12//! The functions [to_ascii_lines] and [image_into_lines] are the simplest way to
13//! convert an image into ASCII. Keep in mind, however, the dimensions taken by
14//! these functions are the exact level that the input image will be resized to,
15//! aspect ration is not respected.
16//!
17//! ### Advanced
18//! The [ASCIIGenerator] struct can be used to have more control over the exact
19//! parameters used.
20
21use std::path::Path;
22
23#[cfg(feature = "color")]
24use color::Color;
25use color::GrayscaleMode;
26pub use image::DynamicImage;
27use image::{GenericImageView, ImageReader};
28#[cfg(feature = "crossterm")]
29pub use terminal::put_in_console;
30pub use {
31    error::ASCIIError,
32    generator::{ASCIIGenerator, ResizeMode},
33};
34
35mod color;
36mod error;
37mod generator;
38mod terminal;
39
40#[derive(Debug, Clone)]
41pub struct Line {
42    chars: Vec<char>,
43    #[cfg(feature = "color")]
44    colors: Vec<Color>,
45}
46
47impl Line {
48    pub fn new(size: usize) -> Self {
49        Line {
50            chars: Vec::with_capacity(size),
51            #[cfg(feature = "color")]
52            colors: vec![Color::white(); size],
53        }
54    }
55
56    pub fn add_char(&mut self, char: char) {
57        self.chars.push(char);
58    }
59
60    pub fn add_color(&mut self, color: Color) {
61        self.colors.push(color);
62    }
63
64    pub fn chars(&self) -> &Vec<char> {
65        &self.chars
66    }
67
68    #[cfg(feature = "color")]
69    pub fn colors(&self) -> &Vec<Color> {
70        &self.colors
71    }
72}
73
74pub fn to_ascii_lines<P: AsRef<Path>>(
75    path: P,
76    grayscale_mode: GrayscaleMode,
77    #[cfg(feature = "color")] with_color: bool,
78) -> Result<Vec<Line>, ASCIIError> {
79    let image = ImageReader::open(path)?.decode()?;
80    image_into_lines(
81        &image,
82        grayscale_mode,
83        #[cfg(feature = "color")]
84        with_color,
85    )
86}
87
88pub fn image_into_lines(
89    image: &DynamicImage,
90    grayscale_mode: GrayscaleMode,
91    #[cfg(feature = "color")] with_color: bool,
92) -> Result<Vec<Line>, ASCIIError> {
93    let mut lines: Vec<Line> =
94        vec![Line::new(image.dimensions().0 as usize); image.dimensions().1 as usize];
95    for (_, y, color) in image.pixels() {
96        let color = Color::from(color.0);
97        let gray = color.grayscale(grayscale_mode);
98
99        lines
100            .get_mut(y as usize)
101            .unwrap()
102            .add_char(char_from_gray(gray));
103        if cfg!(feature = "color") && with_color {
104            lines.get_mut(y as usize).unwrap().add_color(color);
105        }
106    }
107
108    Ok(lines)
109}
110
111fn char_from_gray(gray: u8) -> char {
112    if gray > 230 {
113        ' '
114    } else if gray >= 200 {
115        '.'
116    } else if gray >= 180 {
117        '*'
118    } else if gray >= 160 {
119        ':'
120    } else if gray >= 130 {
121        'o'
122    } else if gray >= 100 {
123        '&'
124    } else if gray >= 70 {
125        '8'
126    } else if gray >= 50 {
127        '#'
128    } else {
129        '@'
130    }
131}