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}