rascii_art/
image_renderer.rs1use std::io;
2
3use ansi_term::Color;
4use image::{
5 DynamicImage,
6 Rgba,
7};
8
9use super::renderer::{
10 RenderOptions,
11 Renderer,
12};
13
14pub struct ImageRenderer<'a> {
15 resource: &'a DynamicImage,
16 options: &'a RenderOptions<'a>,
17}
18
19impl ImageRenderer<'_> {
20 fn get_char_for_pixel(&self, pixel: &Rgba<u8>, maximum: f64) -> &str {
21 let as_grayscale = self.get_grayscale(pixel) / maximum;
22
23 let char_index = (as_grayscale * (self.options.charset.len() as f64 - 1.0)) as usize;
25
26 self.options.charset[if self.options.invert {
27 self.options.charset.len() - 1 - char_index
28 } else {
29 char_index
30 }]
31 }
32
33 fn get_grayscale(&self, pixel: &Rgba<u8>) -> f64 {
34 ((pixel[0] as f64 * 0.299) + (pixel[1] as f64 * 0.587) + (pixel[2] as f64 * 0.114)) / 255.0
35 }
36}
37
38impl<'a> Renderer<'a, DynamicImage> for ImageRenderer<'a> {
39 fn new(resource: &'a DynamicImage, options: &'a RenderOptions<'a>) -> Self {
40 Self { resource, options }
41 }
42
43 fn render(&self, writer: &mut impl io::Write) -> io::Result<()> {
44 let (width, height) = (
45 self.options.width.unwrap_or_else(|| {
46 (self
47 .options
48 .height
49 .expect("Either width or height must be set") as f64
50 * self.resource.width() as f64
51 / self.resource.height() as f64
52 * 2.0)
54 .ceil() as u32
55 }),
56 self.options.height.unwrap_or_else(|| {
57 (self
58 .options
59 .width
60 .expect("Either width or height must be set") as f64
61 * self.resource.height() as f64
62 / self.resource.width() as f64
63 / 2.0)
65 .ceil() as u32
66 }),
67 );
68
69 let image = self.resource.thumbnail_exact(width, height).to_rgba8();
70
71 let mut last_color: Option<Color> = None;
72 let mut current_line = 0;
73 let maximum = image
74 .pixels()
75 .fold(0.0, |acc, pixel| self.get_grayscale(pixel).max(acc));
76 for (_, line, pixel) in image.enumerate_pixels() {
77 if current_line < line {
78 current_line = line;
79
80 if let Some(last_color_value) = last_color {
81 write!(writer, "{}", last_color_value.suffix())?;
82 last_color = None;
83 }
84
85 writeln!(writer)?;
86 }
87
88 if self.options.colored {
89 let color = Color::RGB(pixel[0], pixel[1], pixel[2]);
90
91 if last_color != Some(color) {
92 write!(writer, "{}", color.prefix())?;
93 }
94
95 last_color = Some(color);
96 }
97
98 let char_for_pixel = self.get_char_for_pixel(pixel, maximum);
99 write!(writer, "{char_for_pixel}")?;
100 }
101
102 if let Some(last_color) = last_color {
103 write!(writer, "{}", last_color.suffix())?;
104 }
105
106 writer.flush()?;
107
108 Ok(())
109 }
110
111 fn render_to(&self, buffer: &mut String) -> io::Result<()> {
112 let (width, height) = (
113 self.options.width.unwrap_or_else(|| {
114
115 (self
116 .options
117 .height
118 .expect("Either width or height must be set") as f64
119 * self.resource.width() as f64
120 / self.resource.height() as f64
121 * 2.0)
123 .ceil() as u32
124 }),
125
126 self.options.height.unwrap_or_else(|| {
127 (self
128 .options
129 .width
130 .expect("Either width or height must be set") as f64
131 * self.resource.height() as f64
132 / self.resource.width() as f64
133 / 2.0)
135 .ceil() as u32
136 }),
137
138 );
139
140 let image = self.resource.thumbnail_exact(width, height).to_rgba8();
141
142 let mut last_color: Option<Color> = None;
143 let mut current_line = 0;
144 let maximum = image
145 .pixels()
146 .fold(0.0, |acc, pixel| self.get_grayscale(pixel).max(acc));
147 for (_, line, pixel) in image.enumerate_pixels() {
148 if current_line < line {
149 current_line = line;
150
151 if let Some(last_color_value) = last_color {
152 buffer.push_str(&last_color_value.suffix().to_string()); last_color = None;
155 }
156
157 buffer.push('\n');
158 }
159
160 if self.options.colored {
161 let color = Color::RGB(pixel[0], pixel[1], pixel[2]);
162
163 if last_color != Some(color) {
164 buffer.push_str(&color.prefix().to_string());
165 }
166
167 last_color = Some(color);
168 }
169
170 let char_for_pixel = self.get_char_for_pixel(pixel, maximum);
173 buffer.push_str(char_for_pixel);
174 }
175
176
177 if let Some(last_color) = last_color {
178 buffer.push_str(&last_color.suffix().to_string()); }
181
182 Ok(())
183 }
184
185}