1#![cfg_attr(docsrs, feature(doc_cfg))]
2
3use 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}