use std::io::{BufWriter, Cursor};
use image::{ImageBuffer, Rgba, RgbaImage};
use crate::{hex_to_rgba, GraphConfig, GraphType};
pub fn render(graph_config: &GraphConfig) -> Vec<u8> {
let width = graph_config.width;
let height = graph_config.height;
let graph_data = prepare_graph_data(width, &graph_config.sensor_values);
let mut image = match graph_config.graph_type {
GraphType::Line => render_line_chart(&graph_data, graph_config),
GraphType::LineFill => render_line_chart_filled(&graph_data, graph_config),
};
if !graph_config.border_color.ends_with("00") {
draw_border(&mut image, &graph_config.border_color, width, height);
}
let mut writer = BufWriter::new(Cursor::new(Vec::new()));
image
.write_to(&mut writer, image::ImageOutputFormat::Png)
.unwrap();
writer.into_inner().unwrap().into_inner()
}
fn draw_border(
image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>,
border_color: &str,
width: u32,
height: u32,
) {
let border_color = hex_to_rgba(border_color);
let border_x: i32 = 0;
let border_y: i32 = 0;
let border_width: u32 = width;
let border_height: u32 = height;
imageproc::drawing::draw_hollow_rect_mut(
image,
imageproc::rect::Rect::at(border_x, border_y).of_size(border_width, border_height),
border_color,
);
}
fn prepare_graph_data(width: u32, sensor_values: &Vec<f64>) -> Vec<f64> {
let sensor_values = if sensor_values.len() > width as usize {
sensor_values[(sensor_values.len() - width as usize)..].to_vec()
} else {
sensor_values.to_vec()
};
let mut plot_data: Vec<f64> = vec![0.0; (width) as usize];
plot_data.splice(
(width - sensor_values.len() as u32) as usize..,
sensor_values,
);
plot_data
}
fn render_line_chart(numbers: &[f64], config: &GraphConfig) -> RgbaImage {
let width = config.width;
let height = config.height;
let min_value = config.min_sensor_value.unwrap_or(get_min(numbers));
let max_value = config.max_sensor_value.unwrap_or(get_max(numbers));
let line_width = config.graph_stroke_width;
let line_color = hex_to_rgba(&config.graph_color);
let background_color = hex_to_rgba(&config.background_color);
let mut image = RgbaImage::from_pixel(width, height, background_color);
let half_line_width = (line_width / 2) as f32;
for i in 0..numbers.len() - 1 {
let current_value = numbers[i];
let next_value = numbers[i + 1];
let current_value_normalized = (current_value - min_value) / (max_value - min_value);
let next_value_normalized = (next_value - min_value) / (max_value - min_value);
let img_line_start = current_value_normalized * height as f64;
let img_line_end = next_value_normalized * height as f64;
let x0 = i;
let y0 = height as f64 - img_line_start;
let x1 = i + 1;
let y1 = height as f64 - img_line_end;
for offset in -half_line_width as i32..=half_line_width as i32 {
imageproc::drawing::draw_line_segment_mut(
&mut image,
((x0 as f32) + offset as f32, y0 as f32),
((x1 as f32) + offset as f32, y1 as f32),
line_color,
);
}
}
image
}
fn render_line_chart_filled(numbers: &[f64], config: &GraphConfig) -> RgbaImage {
let width = config.width;
let height = config.height;
let min_value = config.min_sensor_value.unwrap_or(get_min(numbers));
let max_value = config.max_sensor_value.unwrap_or(get_max(numbers));
let line_width = config.graph_stroke_width;
let line_color = hex_to_rgba(&config.graph_color);
let fill_color = line_color;
let background_color = hex_to_rgba(&config.background_color);
let mut image = RgbaImage::from_pixel(width, height, background_color);
let half_line_width = (line_width / 2) as f32;
for i in 0..numbers.len() - 1 {
let current_value = numbers[i];
let next_value = numbers[i + 1];
let current_value_normalized = (current_value - min_value) / (max_value - min_value);
let next_value_normalized = (next_value - min_value) / (max_value - min_value);
let img_line_start = current_value_normalized * height as f64;
let img_line_end = next_value_normalized * height as f64;
let x0 = i;
let y0 = height.saturating_sub(img_line_start as u32);
let x1 = i + 1;
let y1 = height.saturating_sub(img_line_end as u32);
let mut y = y0;
while y < height {
image.put_pixel(x0 as u32, y, fill_color);
y += 1;
}
for offset in -half_line_width as i32..=half_line_width as i32 {
imageproc::drawing::draw_line_segment_mut(
&mut image,
((x0 as f32) + offset as f32, y0 as f32),
((x1 as f32) + offset as f32, y1 as f32),
line_color,
);
}
}
image
}
fn get_min(values: &[f64]) -> f64 {
let mut min = values[0];
for value in values {
if *value < min {
min = *value;
}
}
min
}
fn get_max(values: &[f64]) -> f64 {
let mut max = values[0];
for value in values {
if *value > max {
max = *value;
}
}
max
}