cgrustplot/plots/
image_plot.rs

1//! # Image Plot
2//! Creates an image from a table of (R, G, B) values.
3//! 
4//! # Functions
5//! 
6//! * `image_plot` - Generates an ImagePlotBuilder from a table of RGB.
7//! * `convert_from_hsv` - Converts an HSV table to an RGB table.
8//! 
9
10use crate::{
11    helper::{
12        file::{get_current_dir, save_image, save_to_file},
13        mat_plot_lib::pyplot,
14        rendering::RenderableTextBuilder,
15    },
16    plots::array_plot::array_plot,
17};
18use rayon::prelude::*;
19
20fn hsv_to_rgb(hsv: (u8, u8, u8)) -> (u8, u8, u8) {
21    let (h, s, v) = hsv;
22
23    let h = h as f64 * 360.0 / 255.0; // Scale hue to [0, 360)
24    let s = s as f64 / 255.0;         // Scale saturation to [0, 1]
25    let v = v as f64 / 255.0;         // Scale value to [0, 1]
26
27    let c = v * s; // Chroma
28    let x = c * (1.0 - ((h / 60.0) % 2.0 - 1.0).abs());
29    let m = v - c;
30
31    let (r1, g1, b1) = match h as u16 {
32        0..=59 => (c, x, 0.0),
33        60..=119 => (x, c, 0.0),
34        120..=179 => (0.0, c, x),
35        180..=239 => (0.0, x, c),
36        240..=299 => (x, 0.0, c),
37        300..=359 => (c, 0.0, x),
38        _ => (0.0, 0.0, 0.0), // Default to black if hue is out of range
39    };
40
41    let r = ((r1 + m) * 255.0).round() as u8;
42    let g = ((g1 + m) * 255.0).round() as u8;
43    let b = ((b1 + m) * 255.0).round() as u8;
44    (r, g, b)
45}
46
47/// Builder for an Image Plot
48/// Set various options for the image.
49/// 
50/// # Options
51/// 
52/// * `img` - Input table of (R, G, B) representing the image.
53/// * `path` - Path to save the image to. Default is "./output.png".
54/// 
55#[derive(Clone)]
56pub struct ImagePlotBuilder<'a> {
57    img: &'a Vec<Vec<(u8, u8, u8)>>,
58    path: Option<String>,
59}
60
61/// Internal struct representing built values.
62struct ImagePlot<'a> {
63    img: &'a Vec<Vec<(u8, u8, u8)>>,
64    path: String,
65}
66
67impl<'a> ImagePlotBuilder<'a> {
68    /// Create an array plot from a table of data.
69    fn from<'b: 'a>(img: &'b Vec<Vec<(u8, u8, u8)>>) -> Self {
70        ImagePlotBuilder {
71            img,
72            path: None,
73        }
74    }
75
76    pub fn set_rel_path(&mut self, path: &str) -> &mut Self {
77        if path.contains(".") {
78            self.path = Some(get_current_dir() + path);
79        } else {
80            self.path = Some(get_current_dir() + path + ".png");
81        }
82        self
83    }
84
85    pub fn set_abs_path(&mut self, path: &str) -> &mut Self {
86        if path.contains(".") {
87            self.path = Some(path.to_string());
88        } else {
89            self.path = Some(path.to_string() + ".png");
90        }
91        self
92    }
93
94    fn build(&self) -> ImagePlot {
95        ImagePlot {
96            img: self.img,
97            path: self.path.clone().unwrap_or_else(|| get_current_dir() + &"output.png"),
98        }
99    }
100
101    /// Returns a monochrome text render as a string
102    pub fn as_string(&self) -> String {
103        self.build().as_string()
104    }
105
106    /// Displays a monochrome text render with println
107    pub fn print(&self) {
108        self.build().print();
109    }
110
111    /// Saves a monochrome text render to a file
112    pub fn save_as_text(&self, path: &str) {
113        save_to_file(&self.build().as_string(), path);
114    }
115
116    /// Saves the image to a file.
117    pub fn save(&self) {
118        self.build().save();
119    }
120
121    /// Returns a rendered text builder to render a string
122    pub fn as_image(&self) -> RenderableTextBuilder {
123        RenderableTextBuilder::from(self.build().as_string())
124    }
125
126    /// Displays the plot's data using pyplot
127    pub fn pyplot(&self) {
128        self.build().pyplot(None);
129    }
130
131    /// Saves the plot's data using pyplot
132    pub fn save_pyplot(&self, path: &str) {
133        self.build().pyplot(Some(path));
134    }
135
136    /// Returns the unformatted text content of a plot
137    #[allow(dead_code)]
138    pub(crate) fn plot(&self) -> String {
139        self.build().plot()
140    }
141}
142
143impl<'a> ImagePlot<'a> {
144    fn plot(&self) -> String {
145        let brightnesses: Vec<Vec<u32>> = self.img.par_iter().map(|row| row.iter().map(|p| p.0 as u32 + p.1 as u32 + p.2 as u32).collect()).collect();
146        array_plot(&brightnesses)
147        .set_axes(false)
148        .set_title(&self.path)
149        .as_string()
150    }
151
152    fn as_string(&self) -> String {
153        self.plot()
154    }
155
156    fn print(&self) {
157        println!("{}", self.as_string());
158    }
159
160    fn pyplot(&self, path: Option<&str>) {
161        let command = format!("imshow(np.array({:?}))", self.img);
162        pyplot(&command, None, None, None, path);
163    }
164
165    fn save(&self) {
166        save_image(&self.img, &self.path);
167    }
168}
169
170/// Creates an image from a table of (R, G, B) values.
171/// 
172/// # Example
173/// 
174/// ```
175/// use cgrustplot::plots::image_plot::image_plot;
176/// 
177/// let image: Vec<Vec<(u8, u8, u8)>> = (0..1080).map(|r| (0..1920).map(|c| (0.01 * r as f64).sin() * (0.01 * c as f64).sin()).map(|x| (127. * (1. + x)) as u8).map(|x| (x, x, x)).collect()).collect();
178/// image_plot(&image).set_rel_path("testoutput/doctest_image_plot.png").save();
179/// 
180/// ```
181/// 
182/// # Options
183/// 
184/// * `img` - Input table of (R, G, B) representing the image.
185/// * `path` - Path to save the image to. Default is "./output.png".
186/// 
187pub fn image_plot<'a>(img: &'a Vec<Vec<(u8, u8, u8)>>) -> ImagePlotBuilder<'a> {
188    ImagePlotBuilder::from(img)
189}
190
191/// Converts a HSV image (represented as a table of (H, S, V)) to an RGB image.
192pub fn convert_from_hsv(hsv: &Vec<Vec<(u8, u8, u8)>>) -> Vec<Vec<(u8, u8, u8)>> {
193    hsv.par_iter().map(|row| row.into_iter().map(|pixel| hsv_to_rgb(*pixel)).collect()).collect()
194}
195
196/// Converts an RGB image (represented as a table of (R, G, B)) to a smaller image,
197/// scaled down by scale_factor
198pub fn downsample(img: &Vec<Vec<(u8, u8, u8)>>, scale_factor: f64) -> Vec<Vec<(u8, u8, u8)>> {
199    let img_h = img.len();
200    let img_w = if img.len() != 0 {img[0].len()} else {0};
201
202    let o_h = (scale_factor * img_h as f64) as usize;
203    let o_w = (scale_factor * img_w as f64) as usize;
204
205    let mut o: Vec<Vec<(u8, u8, u8)>> = Vec::with_capacity(o_w * o_h);
206
207    for y in 0..o_h {
208        for x in 0..o_w {
209            let mut sum_r = 0;
210            let mut sum_g = 0;
211            let mut sum_b = 0;
212            let mut count = 0;
213
214            for r in ((y as f64 * scale_factor).floor() as usize)..(((y + 1) as f64 * scale_factor).ceil() as usize) {
215                for c in ((x as f64 * scale_factor).floor() as usize)..(((x + 1) as f64 * scale_factor).ceil() as usize) {
216                    let (r, g, b) = img[r][c];
217                    sum_r += r as u32;
218                    sum_g += g as u32;
219                    sum_b += b as u32;
220                    count += 1;
221                }
222            }
223
224            let process = |sum| (sum as f64 / count as f64).clamp(0., 255.) as u8;
225
226            o[y][x] = (process(sum_r), process(sum_g), process(sum_b));
227        }
228    }
229
230    o
231}