tapciify/
background_string.rs

1//! Use text for background on light pixels
2
3#[cfg(feature = "rayon")]
4use rayon::prelude::*;
5
6use crate::threshold_utils::{ThresholdPixel, DEFAULT_THRESHOLD};
7use crate::{AsciiArt, AsciiArtPixel, SizeError};
8
9/// Convert image into ASCII art with text on the background
10pub trait BackgroundStringArtConverter {
11    /// Convert image into ASCII art with text on the background
12    ///
13    /// # Examples
14    ///
15    /// ```
16    /// use std::error::Error;
17    ///
18    /// use tapciify::{CustomRatioResize, DEFAULT_FONT_RATIO};
19    /// use tapciify::background_string::BackgroundStringArtConverter;
20    ///
21    /// # use image::imageops::FilterType;
22    ///
23    /// # fn main() -> Result<(), Box<dyn Error>> {
24    /// let img = image::open("./assets/examples/ferris.webp")?;
25    ///
26    /// let result = img
27    ///     .resize_custom_ratio(Some(64), None, DEFAULT_FONT_RATIO, FilterType::Triangle)
28    ///     .background_string_art("hello world! ", false)?;
29    ///
30    /// println!("{}", result);
31    /// # Ok(())
32    /// # }
33    /// ```
34    fn background_string_art(&self, string: &str, colored: bool) -> Result<AsciiArt, SizeError>;
35}
36
37impl BackgroundStringArtConverter for image::DynamicImage {
38    fn background_string_art(&self, string: &str, colored: bool) -> Result<AsciiArt, SizeError> {
39        self.clone()
40            .into_rgba8()
41            .background_string_art(string, colored)
42    }
43}
44
45impl BackgroundStringArtConverter for image::RgbImage {
46    fn background_string_art(&self, string: &str, colored: bool) -> Result<AsciiArt, SizeError> {
47        if self.width() == 0 || self.height() == 0 {
48            return Err(SizeError);
49        }
50
51        #[cfg(feature = "rayon")]
52        let iter = self.par_pixels();
53        #[cfg(not(feature = "rayon"))]
54        let iter = self.pixels();
55
56        let characters = iter
57            .enumerate()
58            .map(|(index, pixel)| AsciiArtPixel {
59                character: match pixel.threshold_pixel(DEFAULT_THRESHOLD) {
60                    true => string.chars().nth(index % string.chars().count()).unwrap(),
61                    false => ' ',
62                },
63                r: pixel.0[0],
64                g: pixel.0[1],
65                b: pixel.0[2],
66                a: 255,
67            })
68            .collect::<Vec<AsciiArtPixel>>();
69
70        Ok(AsciiArt::new(
71            characters,
72            self.width(),
73            self.height(),
74            colored,
75        ))
76    }
77}
78
79impl BackgroundStringArtConverter for image::RgbaImage {
80    fn background_string_art(&self, string: &str, colored: bool) -> Result<AsciiArt, SizeError> {
81        if self.width() == 0 || self.height() == 0 {
82            return Err(SizeError);
83        }
84
85        #[cfg(feature = "rayon")]
86        let iter = self.par_pixels();
87        #[cfg(not(feature = "rayon"))]
88        let iter = self.pixels();
89
90        let characters = iter
91            .enumerate()
92            .map(|(index, pixel)| AsciiArtPixel {
93                character: match pixel.threshold_pixel(DEFAULT_THRESHOLD) {
94                    true => string.chars().nth(index % string.chars().count()).unwrap(),
95                    false => ' ',
96                },
97                r: pixel.0[0],
98                g: pixel.0[1],
99                b: pixel.0[2],
100                a: pixel.0[3],
101            })
102            .collect::<Vec<AsciiArtPixel>>();
103
104        Ok(AsciiArt::new(
105            characters,
106            self.width(),
107            self.height(),
108            colored,
109        ))
110    }
111}
112
113impl BackgroundStringArtConverter for image::GrayImage {
114    fn background_string_art(&self, string: &str, colored: bool) -> Result<AsciiArt, SizeError> {
115        if self.width() == 0 || self.height() == 0 {
116            return Err(SizeError);
117        }
118
119        #[cfg(feature = "rayon")]
120        let iter = self.par_pixels();
121        #[cfg(not(feature = "rayon"))]
122        let iter = self.pixels();
123
124        let characters = iter
125            .enumerate()
126            .map(|(index, pixel)| AsciiArtPixel {
127                character: match pixel.threshold_pixel(DEFAULT_THRESHOLD) {
128                    true => string.chars().nth(index % string.chars().count()).unwrap(),
129                    false => ' ',
130                },
131                r: pixel.0[0],
132                g: pixel.0[0],
133                b: pixel.0[0],
134                a: 255,
135            })
136            .collect::<Vec<AsciiArtPixel>>();
137
138        Ok(AsciiArt::new(
139            characters,
140            self.width(),
141            self.height(),
142            colored,
143        ))
144    }
145}
146
147impl BackgroundStringArtConverter for image::GrayAlphaImage {
148    fn background_string_art(&self, string: &str, colored: bool) -> Result<AsciiArt, SizeError> {
149        if self.width() == 0 || self.height() == 0 {
150            return Err(SizeError);
151        }
152
153        #[cfg(feature = "rayon")]
154        let iter = self.par_pixels();
155        #[cfg(not(feature = "rayon"))]
156        let iter = self.pixels();
157
158        let characters = iter
159            .enumerate()
160            .map(|(index, pixel)| AsciiArtPixel {
161                character: match pixel.threshold_pixel(DEFAULT_THRESHOLD) {
162                    true => string.chars().nth(index % string.chars().count()).unwrap(),
163                    false => ' ',
164                },
165                r: pixel.0[0],
166                g: pixel.0[0],
167                b: pixel.0[0],
168                a: pixel.0[1],
169            })
170            .collect::<Vec<AsciiArtPixel>>();
171
172        Ok(AsciiArt::new(
173            characters,
174            self.width(),
175            self.height(),
176            colored,
177        ))
178    }
179}