use gifed::{writer::ImageBuilder, Gif, StandardGif}; #[rustfmt::skip] pub const DARK_PALETTE: &[u8] = &[ 0, 0, 0, // Background - Black 192, 192, 192, // Graphline - Mostly White 64, 64, 64, // Gridlines - Dark gray 32, 32, 32, // Minor Gridlines - Darker gray 144, 144, 255, // Primary 2 Colour - Blue 48, 192, 48, // Secondary 2 Colour - Green 96, 96, 224, // Primary Underfill - Light Blue 48, 128, 48, // Secondary Underfill - Lesser Green 144, 144, 144 // Graphline Underfill - 16 * 9 gray ]; const BACKGROUND: u8 = 0; const LINE: u8 = 1; const GRIDLINE: u8 = 2; const MINOR_GRIDLINE: u8 = 3; const LINE1: u8 = 4; const LINE2: u8 = 5; const LINE1_FILL: u8 = 6; const LINE2_FILL: u8 = 7; const LINE_FILL: u8 = 8; const WIDTH: usize = 256; const HEIGHT: usize = 160; const SIZE: usize = WIDTH * HEIGHT; pub enum Style { Line, UnderfilledLine, } pub fn make_1line(min: usize, max: usize, values: &[Option], style: Style) -> Gif { let range = max - min; // this assumes a range of values that is >1 per pixel let vpp = range as f32 / HEIGHT as f32; let mut raster = vec![0; SIZE]; let mut standard = Gif::new(WIDTH as u16, HEIGHT as u16); draw_grid(&mut raster); draw_line(&mut raster, values, vpp, LINE); let plt = match style { Style::Line => { draw_line(&mut raster, values, vpp, LINE); &DARK_PALETTE[0..12] } Style::UnderfilledLine => { draw_line_underfill(&mut raster, values, vpp, LINE_FILL, LINE); &DARK_PALETTE } }; standard.set_palette(Some(plt.try_into().unwrap())); standard.push( ImageBuilder::new(WIDTH as u16, HEIGHT as u16) .build(raster) .unwrap(), ); standard } #[derive(Clone, Copy, Debug, PartialEq)] pub enum TwoLineOrder { SeriesAFirst, SeriesBFirst, } pub fn make_2line( min: usize, max: usize, values1: &[Option], values2: &[Option], draw_order: TwoLineOrder, ) -> Gif { let range = max - min; // this assumes a range of values that is >1 per pixel let vpp = range as f32 / HEIGHT as f32; let mut raster = vec![0; SIZE]; draw_grid(&mut raster); match draw_order { TwoLineOrder::SeriesAFirst => { draw_line_underfill(&mut raster, values1, vpp, LINE1, LINE1_FILL); draw_line_underfill(&mut raster, values2, vpp, LINE2, LINE2_FILL); } TwoLineOrder::SeriesBFirst => { draw_line_underfill(&mut raster, values2, vpp, LINE2, LINE2_FILL); draw_line_underfill(&mut raster, values1, vpp, LINE1, LINE1_FILL); } } let mut standard = Gif::new(WIDTH as u16, HEIGHT as u16); standard.set_palette(Some(DARK_PALETTE.try_into().unwrap())); standard.push( ImageBuilder::new(WIDTH as u16, HEIGHT as u16) .build(raster) .unwrap(), ); standard } fn draw_grid(raster: &mut [u8]) { // Draw Divisions // we want a gridline every 16 pixels, but not the bottom // or top, so only 8. for div in 1..=9 { let y_val = div * 16; let grid = if div % 2 == 0 { GRIDLINE } else { MINOR_GRIDLINE }; for x in 0..WIDTH { raster[y_val * WIDTH + x] = grid; } } } fn draw_line(raster: &mut [u8], values: &[Option], vpp: f32, colour: u8) { // Draw Line // this will be discontinuous and i think that's okay. we // could make it a proper line by keeping track what value // was last and drawing the whole vertical there for (x, maybe) in values.iter().enumerate() { if let Some(value) = maybe { let value_height = (*value as f32 / vpp) as usize; if value_height > (HEIGHT - 1) { continue; } let y_val = (HEIGHT - 1) - value_height; raster[y_val * WIDTH + x] = colour; } } } fn draw_line_underfill( raster: &mut [u8], values: &[Option], vpp: f32, colour: u8, colour_fill: u8, ) { for (x, maybe) in values.iter().enumerate() { if let Some(value) = maybe { let value_height = (*value as f32 / vpp) as usize; if value_height > (HEIGHT - 1) { continue; } let y_val = (HEIGHT - 1) - value_height; for y in y_val + 1..HEIGHT { raster[y * WIDTH + x] = colour_fill; } raster[y_val * WIDTH + x] = colour; } } }