ascii_izer/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
#![cfg_attr(docsrs, feature(doc_cfg))]

//! > **An image to ASCII art library for rust**
//! ## Features
//! * Color \[default\]: Processes the image color alongside the pixels during
//!   ASCII generation.
//! * Crossterm: Provides a simple method to put the ASCII into the console via
//!   crossterm. This does not output color
//!
//! ## Usage
//! ### Simple
//! The functions [to_ascii_lines] and [image_into_lines] are the simplest way to
//! convert an image into ASCII. Keep in mind, however, the dimensions taken by
//! these functions are the exact level that the input image will be resized to,
//! aspect ration is not respected.
//!
//! ### Advanced
//! The [ASCIIGenerator] struct can be used to have more control over the exact
//! parameters used.

use std::path::Path;

#[cfg(feature = "color")]
use color::Color;
use color::GrayscaleMode;
pub use image::DynamicImage;
use image::{GenericImageView, ImageReader};
#[cfg(feature = "crossterm")]
pub use terminal::put_in_console;
pub use {
    error::ASCIIError,
    generator::{ASCIIGenerator, ResizeMode},
};

mod color;
mod error;
mod generator;
mod terminal;

#[derive(Debug, Clone)]
pub struct Line {
    chars: Vec<char>,
    #[cfg(feature = "color")]
    colors: Vec<Color>,
}

impl Line {
    pub fn new(size: usize) -> Self {
        Line {
            chars: Vec::with_capacity(size),
            #[cfg(feature = "color")]
            colors: vec![Color::white(); size],
        }
    }

    pub fn add_char(&mut self, char: char) {
        self.chars.push(char);
    }

    pub fn add_color(&mut self, color: Color) {
        self.colors.push(color);
    }

    pub fn chars(&self) -> &Vec<char> {
        &self.chars
    }

    #[cfg(feature = "color")]
    pub fn colors(&self) -> &Vec<Color> {
        &self.colors
    }
}

pub fn to_ascii_lines<P: AsRef<Path>>(
    path: P,
    grayscale_mode: GrayscaleMode,
    #[cfg(feature = "color")] with_color: bool,
) -> Result<Vec<Line>, ASCIIError> {
    let image = ImageReader::open(path)?.decode()?;
    image_into_lines(
        &image,
        grayscale_mode,
        #[cfg(feature = "color")]
        with_color,
    )
}

pub fn image_into_lines(
    image: &DynamicImage,
    grayscale_mode: GrayscaleMode,
    #[cfg(feature = "color")] with_color: bool,
) -> Result<Vec<Line>, ASCIIError> {
    let mut lines: Vec<Line> =
        vec![Line::new(image.dimensions().0 as usize); image.dimensions().1 as usize];
    for (_, y, color) in image.pixels() {
        let color = Color::from(color.0);
        let gray = color.grayscale(grayscale_mode);

        lines
            .get_mut(y as usize)
            .unwrap()
            .add_char(char_from_gray(gray));
        if cfg!(feature = "color") && with_color {
            lines.get_mut(y as usize).unwrap().add_color(color);
        }
    }

    Ok(lines)
}

fn char_from_gray(gray: u8) -> char {
    if gray > 230 {
        ' '
    } else if gray >= 200 {
        '.'
    } else if gray >= 180 {
        '*'
    } else if gray >= 160 {
        ':'
    } else if gray >= 130 {
        'o'
    } else if gray >= 100 {
        '&'
    } else if gray >= 70 {
        '8'
    } else if gray >= 50 {
        '#'
    } else {
        '@'
    }
}