use image::{ImageError, RgbImage};
use std::ops::Range;
use crate::math::{Bounds, Point};
use super::color::{BLACK, Color, GREY, WHITE};
use super::plot_frame::PlotFrame;
use super::shapes::{Circle, Orientation};
pub struct Plot {
pub width: i64, pub height: i64, pub frame: PlotFrame,
bg_color: Color,
canvas_color: Color,
canvas_bounds: Bounds<i64>,
frame_color: Option<Color>,
image: image::RgbImage,
}
impl Plot {
pub fn new(width: u32, height: u32) -> Plot {
let mut p = Plot {
bg_color: WHITE,
canvas_color: GREY,
width: width as i64,
height: height as i64,
canvas_bounds: (0..(width as i64), 0..(height as i64)).into(),
frame_color: Some(BLACK),
frame: PlotFrame::new(
(0..(width as i64), 0..(height as i64)).into(),
(0.0..1.0, 0.0..1.0).into()),
image: RgbImage::new(width, height),
}
.with_drawing_bounds(0.05..0.95, 0.05..0.95);
p.clear();
p
}
pub fn clear(&mut self) {
let x_bounds = self.canvas_bounds.x.clone();
let y_bounds = self.canvas_bounds.y.clone();
for y in 0..self.height {
for x in 0..self.width {
if x_bounds.contains(&x) && y_bounds.contains(&y) {
self.image.put_pixel(x as u32, y as u32, self.canvas_color);
} else {
self.image.put_pixel(x as u32, y as u32, self.bg_color);
}
}
}
if let Some(color) = self.frame_color {
for y in y_bounds.clone() {
self.image.put_pixel(x_bounds.start as u32, y as u32, color);
self.image.put_pixel(x_bounds.end as u32, y as u32, color);
}
for x in x_bounds.clone() {
self.image.put_pixel(x as u32, y_bounds.start as u32, color);
self.image.put_pixel(x as u32, y_bounds.end as u32, color);
}
}
}
pub fn draw_line<P>(&mut self, point1: P, point2: P, color: Color)
where P: Into<(f64, f64)> {
let p1 = self.frame.pt_to_px(point1.into());
let p2 = self.frame.pt_to_px(point2.into());
let dx = p2.0 - p1.0;
let dy = p2.1 - p1.1;
if dx.abs() > dy.abs() {
if dx > 0 {
for x in p1.0..p2.0 {
let y = p1.1 + dy * (x - p1.0) / dx;
self.put_pixel_safe(x, y, color);
}
} else {
for x in p2.0..p1.0 {
let y = p1.1 + dy * (x - p1.0) / dx;
self.put_pixel_safe(x, y, color);
}
}
} else {
if dy > 0 {
for y in p1.1..p2.1 {
let x = p1.0 + dx * (y - p1.1) / dy;
self.put_pixel_safe(x, y, color);
}
} else {
for y in p2.1..p1.1 {
let x = p1.0 + dx * (y - p1.1) / dy;
self.put_pixel_safe(x, y, color);
}
}
}
self.put_pixel_safe(p2.0, p2.1, color);
}
pub fn draw_pixel_line(&mut self, p1: (f64, f64), p2: (f64, f64), color: Color) {
let dx = p2.0 - p1.0;
let dy = p2.1 - p1.1;
if dx.abs() > dy.abs() {
if dx > 0.0 {
let px_x_1 = p1.0.round() as i64;
let px_x_2 = p2.0.round() as i64;
for x in px_x_1..px_x_2 {
let y = p1.1 + dy * (x as f64 - p1.0) / dx;
self.put_pixel_safe(x, y.round() as i64, color);
}
} else {
let px_x_1 = p1.0.round() as i64;
let px_x_2 = p2.0.round() as i64;
for x in px_x_2..px_x_1 {
let y = p1.1 + dy * (x as f64 - p1.0) / dx;
self.put_pixel_safe(x, y.round() as i64, color);
}
}
} else {
if dy > 0.0 {
let px_y_1 = p1.1.round() as i64;
let px_y_2 = p2.1.round() as i64;
for y in px_y_1..px_y_2 {
let x = p1.0 + dx * (y as f64 - p1.1) / dy;
self.put_pixel_safe(x.round() as i64, y, color);
}
} else {
let px_y_1 = p1.1.round() as i64;
let px_y_2 = p2.1.round() as i64;
for y in px_y_2..px_y_1 {
let x = p1.0 + dx * (y as f64 - p1.1) / dy;
self.put_pixel_safe(x.round() as i64, y, color);
}
}
}
self.put_pixel_safe(p2.0.round() as i64, p2.1.round() as i64, BLACK);
}
pub fn draw_orientations(&mut self, orientation: &Orientation, positions: &[Point], angles: &[f64]) {
for i in 0..positions.len() {
orientation.draw(self, positions[i], angles[i]);
}
}
pub fn draw_circles(&mut self, circle: &Circle, positions: &[Point]) {
for i in 0..positions.len() {
circle.draw(self, positions[i]);
}
}
pub fn save(&self, filename: &str) -> Result<(), ImageError> {
self.image.save(filename)
}
pub fn put_pixel_safe(&mut self, x: i64, y: i64, c: Color) {
if self.canvas_bounds.x.contains(&x) && self.canvas_bounds.y.contains(&y) {
self.image.put_pixel(x as u32, y as u32, c)
}
}
pub fn with_drawing_bounds<R: Into<Range<f64>>>(mut self, x_bounds: R, y_bounds: R) -> Self {
let w = self.width as f64;
let h = self.height as f64;
let x = x_bounds.into();
let y = y_bounds.into();
self.canvas_bounds.x = ((x.start * w + 0.5) as i64)..((x.end * w + 0.5) as i64);
self.canvas_bounds.y = ((y.start * h + 0.5) as i64)..((y.end * h + 0.5) as i64);
self.frame.set_drawing_bounds(self.canvas_bounds.x.clone(), self.canvas_bounds.y.clone());
self
}
pub fn with_plotting_range<R: Into<Range<f64>>>(mut self, x_range: R, y_range: R) -> Self {
self.frame.set_plotting_range(x_range, y_range);
self
}
pub fn with_bg_color(mut self, color: Color) -> Self {
self.bg_color = color;
self
}
pub fn with_canvas_color(mut self, color: Color) -> Self {
self.canvas_color = color;
self
}
pub fn with_frame_color(mut self, color: Option<Color>) -> Self {
self.frame_color = color;
self
}
}