ascim/
lib.rs

1//! Crate-level documentation pulled from README
2#![doc = include_str!("../README.md")]
3
4mod argparse;
5mod image_processing;
6mod print_image;
7
8use argparse::Args;
9use image_processing::{ImageData, load_and_resize_image, make_grayscale};
10
11use print_image::{get_ascii_char, get_sobel, get_sobel_angle_char, rgb_to_hsv};
12use std::f64::consts::PI;
13
14/// Represents an RGB color with floating point values between 0.0 and 1.0
15///
16/// # Fields
17/// * `r` - Red component (0.0 to 1.0)
18/// * `g` - Green component (0.0 to 1.0)
19/// * `b` - Blue component (0.0 to 1.0)
20#[derive(Debug, Clone, Copy)]
21pub struct RGBColor {
22    pub r: f64,
23    pub g: f64,
24    pub b: f64,
25}
26
27///
28/// * `file_path` - Path to the input image file
29/// * `max_width` - Maximum width of the output ASCII art in characters
30/// * `max_height` - Maximum height of the output ASCII art in characters
31/// * `character_ratio` - Aspect ratio correction for terminal characters (typically 2.0)
32/// * `edge_threshold` - Threshold for edge detection (range: 0.0 to 4.0)
33///
34pub struct Arguments {
35    pub file_path: String,
36    pub max_width: usize,
37    pub max_height: usize,
38    pub character_ratio: f64,
39    pub edge_threshold: f64,
40}
41
42/// Represents an ASCII art image with color information
43///
44/// # Fields
45/// * `height` - Height of the image in characters
46/// * `width` - Width of the image in characters  
47/// * `threshold` - Edge detection threshold value
48/// * `character_ratio` - Aspect ratio correction for terminal characters
49/// * `converted_image` - 2D vector containing RGB colors and ASCII characters
50#[derive(Debug, Clone)]
51pub struct AsciImage {
52    pub height: usize,
53    pub width: usize,
54    pub threshold: f64,
55    pub character_ratio: f64,
56    pub converted_image: Vec<Vec<(RGBColor, char)>>, // 2D array of converted image
57}
58
59impl AsciImage {
60    /// Creates a new AsciImage instance from command line arguments
61    ///
62    /// # Arguments
63    /// * `args` - Command line arguments containing image path and conversion options
64    ///
65    /// # Returns
66    /// * `AsciImage` - A new AsciImage instance with the converted image data
67    ///
68    ///  # Example
69    /// ```rust
70    /// let args = Arguments {
71    ///     file_path: String::from("path/to/image.jpg"),
72    ///     max_width: 80,
73    ///     max_height: 40,
74    ///     character_ratio: 2.0,
75    ///     edge_threshold: 1.0,
76    /// };
77    ///
78    /// # Panics
79    /// * If the image fails to load or process
80    pub fn from_args(args: &Arguments) -> Self {
81        let img_data = load_and_resize_image(&Args {
82            file_path: args.file_path.clone(),
83            max_width: args.max_width,
84            max_height: args.max_height,
85            character_ratio: args.character_ratio,
86            edge_threshold: args.edge_threshold,
87        })
88        .expect("Failed to load image");
89        let threshold = args.edge_threshold;
90        let character_ratio = args.character_ratio;
91        let converted_image = Self::convert_to_ascii(&img_data, threshold);
92
93        AsciImage {
94            height: img_data.height,
95            width: img_data.width,
96            threshold,
97            character_ratio,
98            converted_image,
99        }
100    }
101
102    /// Prints the ASCII art with ANSI colors to the console
103    ///
104    /// # Example
105    /// ```rust
106    /// use ascim::AsciImage;
107    ///
108    /// let args = Arguments {
109    ///     file_path: String::from("examples/image.jpg"),
110    ///     max_width: 80,
111    ///     max_height: 40,
112    ///     character_ratio: 2.0,
113    ///     edge_threshold: 1.0,
114    /// };
115    ///
116    /// let ascii = AsciImage::from_args(&args);
117    /// ascii.print(); // Prints the colored ASCII art to console
118    /// ```
119    pub fn print(&self) {
120        for row in &self.converted_image {
121            for &(color, ch) in row {
122                // Convert RGB values (0.0-1.0) to 0-255 range
123                let r = (color.r * 255.0) as u8;
124                let g = (color.g * 255.0) as u8;
125                let b = (color.b * 255.0) as u8;
126
127                // Print colored character using ANSI escape codes
128                print!("\x1b[38;2;{};{};{}m{}\x1b[0m", r, g, b, ch);
129            }
130            println!();
131        }
132    }
133
134    /// Converts the image data to ASCII characters based on the threshold and character ratio.
135    fn convert_to_ascii(image_data: &ImageData, threshold: f64) -> Vec<Vec<(RGBColor, char)>> {
136        let grayscale = make_grayscale(image_data);
137
138        let (sobel_x, sobel_y) = if threshold < 4.0 {
139            get_sobel(&grayscale, image_data.width, image_data.height)
140        } else {
141            (
142                vec![0.0; image_data.width * image_data.height],
143                vec![0.0; image_data.width * image_data.height],
144            )
145        };
146
147        // Initialize the ascii_image with default values
148        let mut ascii_image = vec![
149            vec![
150                (
151                    RGBColor {
152                        r: 0.0,
153                        g: 0.0,
154                        b: 0.0
155                    },
156                    ' '
157                );
158                image_data.width
159            ];
160            image_data.height
161        ];
162
163        for y in 0..image_data.height {
164            for x in 0..image_data.width {
165                let idx = y * image_data.width + x;
166                let pixel_idx = idx * 4;
167
168                let r = image_data.data[pixel_idx] as f64 / 255.0;
169                let g = image_data.data[pixel_idx + 1] as f64 / 255.0;
170                let b = image_data.data[pixel_idx + 2] as f64 / 255.0;
171
172                let r_f = r as f64 / 255.0;
173                let g_f = g as f64 / 255.0;
174                let b_f = b as f64 / 255.0;
175
176                let hsv = rgb_to_hsv(r_f, g_f, b_f);
177                let grayscale_val = hsv.value * hsv.value;
178
179                let sx = sobel_x[idx];
180                let sy = sobel_y[idx];
181                let square_sobel_magnitude = sx * sx + sy * sy;
182
183                let ascii_char = if square_sobel_magnitude >= threshold * threshold {
184                    let sobel_angle = sy.atan2(sx) * 180.0 / PI;
185                    get_sobel_angle_char(sobel_angle)
186                } else {
187                    get_ascii_char(grayscale_val)
188                };
189
190                // Store the color and character in the ascii_image array
191                ascii_image[y][x] = (RGBColor { r, g, b }, ascii_char);
192            }
193        }
194
195        ascii_image
196    }
197}