figure.rs
6.2 kB · rust · 195 lines
1use mrlycore::colors::{BLUE, LIGHT, ORANGE, WHITE};2use mrlycore::{io, png};3use std::path::Path;45pub const WIDTH: usize = 1760;6pub const HEIGHT: usize = 736;7const SURF: [u8; 4] = [WHITE.r, WHITE.g, WHITE.b, 255];8const NAVY: [u8; 4] = [BLUE.r, BLUE.g, BLUE.b, 255];9const RUST: [u8; 4] = [ORANGE.r, ORANGE.g, ORANGE.b, 255];10const GREY: [u8; 4] = [LIGHT.dim.r, LIGHT.dim.g, LIGHT.dim.b, 255];11const FAINT: [u8; 4] = [LIGHT.line.r, LIGHT.line.g, LIGHT.line.b, 255];12const INK: [u8; 4] = [LIGHT.fg.r, LIGHT.fg.g, LIGHT.fg.b, 255];1314pub struct Series {15 pub spectral: Vec<f64>,16 pub walker: Vec<f64>,17 pub fractal: Vec<f64>,18}1920struct Frame {21 left: f64,22 right: f64,23 top: f64,24 bottom: f64,25 x: (f64, f64),26 y: (f64, f64),27}2829impl Frame {30 fn place(&self, x: f64, y: f64) -> (f64, f64) {31 let across = (x - self.x.0) / (self.x.1 - self.x.0);32 let up = (y - self.y.0) / (self.y.1 - self.y.0);33 (34 self.left + across * (self.right - self.left),35 self.bottom - up * (self.bottom - self.top),36 )37 }38}3940struct Canvas {41 pixels: Vec<[u8; 4]>,42}4344impl Canvas {45 fn blend(&mut self, x: f64, y: f64, colour: [u8; 4], alpha: f64) {46 if alpha <= 0.0 || x < 0.0 || y < 0.0 || x >= WIDTH as f64 || y >= HEIGHT as f64 {47 return;48 }49 let seat = y as usize * WIDTH + x as usize;50 for channel in 0..3 {51 let under = self.pixels[seat][channel] as f64;52 self.pixels[seat][channel] =53 (under + (colour[channel] as f64 - under) * alpha).round() as u8;54 }55 }5657 fn disc(&mut self, cx: f64, cy: f64, radius: f64, colour: [u8; 4]) {58 let reach = radius + 2.0;59 let mut y = (cy - reach).max(0.0).floor();60 while y <= cy + reach {61 let mut x = (cx - reach).max(0.0).floor();62 while x <= cx + reach {63 let away = (x + 0.5 - cx).hypot(y + 0.5 - cy);64 self.blend(x, y, colour, (radius + 0.5 - away).clamp(0.0, 1.0));65 x += 1.0;66 }67 y += 1.0;68 }69 }7071 fn square(&mut self, cx: f64, cy: f64, half: f64, colour: [u8; 4]) {72 let mut y = (cy - half).max(0.0).floor();73 while y <= cy + half {74 let mut x = (cx - half).max(0.0).floor();75 while x <= cx + half {76 self.blend(x, y, colour, 1.0);77 x += 1.0;78 }79 y += 1.0;80 }81 }8283 fn line(&mut self, x0: f64, y0: f64, x1: f64, y1: f64, colour: [u8; 4], thick: f64) {84 let span = (x1 - x0).hypot(y1 - y0).max(1.0);85 let steps = (span * 2.0).ceil() as usize;86 for step in 0..=steps {87 let part = step as f64 / steps as f64;88 let x = x0 + (x1 - x0) * part;89 let y = y0 + (y1 - y0) * part;90 self.square(x, y, thick / 2.0, colour);91 }92 }9394 fn star(&mut self, cx: f64, cy: f64, radius: f64, colour: [u8; 4]) {95 for spike in 0..5 {96 let angle = -std::f64::consts::FRAC_PI_2 + spike as f64 * std::f64::consts::TAU / 5.0;97 self.line(98 cx,99 cy,100 cx + radius * angle.cos(),101 cy + radius * angle.sin(),102 colour,103 2.0,104 );105 }106 }107108 fn axes(&mut self, frame: &Frame, along: &[f64], up: &[f64]) {109 self.line(110 frame.left,111 frame.bottom,112 frame.right,113 frame.bottom,114 INK,115 1.5,116 );117 self.line(frame.left, frame.top, frame.left, frame.bottom, INK, 1.5);118 for value in along {119 let (x, y) = frame.place(*value, frame.y.0);120 self.line(x, y, x, y + 6.0, INK, 1.5);121 }122 for value in up {123 let (x, y) = frame.place(frame.x.0, *value);124 self.line(x - 6.0, y, x, y, INK, 1.5);125 self.line(x, y, frame.right, y, FAINT, 1.0);126 }127 }128129 fn bracket(&mut self, frame: &Frame, from: f64, to: f64, height: f64) {130 let (x0, y0) = frame.place(from, height);131 let (x1, _) = frame.place(to, height);132 self.line(x0, y0, x1, y0, INK, 1.0);133 self.line(x0, y0, x0, y0 + 7.0, INK, 1.0);134 self.line(x1, y0, x1, y0 + 7.0, INK, 1.0);135 }136}137138pub fn render(series: &Series) -> Vec<[u8; 4]> {139 let mut canvas = Canvas {140 pixels: vec![SURF; WIDTH * HEIGHT],141 };142 let left = Frame {143 left: 110.0,144 right: 830.0,145 top: 70.0,146 bottom: 650.0,147 x: (-0.5, 8.5),148 y: (1.9, 2.9),149 };150 let right = Frame {151 left: 980.0,152 right: 1700.0,153 top: 70.0,154 bottom: 650.0,155 x: (1.3, 2.9),156 y: (0.9, 2.7),157 };158 let ticks: Vec<f64> = (0..9).map(|index| index as f64).collect();159 canvas.axes(&left, &ticks, &[1.9, 2.1, 2.3, 2.5, 2.7, 2.9]);160 let (x0, y0) = left.place(left.x.0, 2.0);161 let (x1, _) = left.place(left.x.1, 2.0);162 canvas.line(x0, y0, x1, y0, GREY, 1.5);163 canvas.bracket(&left, 2.0, 3.0, 2.71);164 canvas.bracket(&left, 4.0, 5.0, 2.28);165 for (index, value) in series.spectral.iter().enumerate() {166 let (x, y) = left.place(index as f64, *value);167 canvas.disc(x, y, 7.0, NAVY);168 }169 for (index, value) in series.walker.iter().enumerate() {170 let (x, y) = left.place(index as f64, *value);171 canvas.square(x, y, 5.0, RUST);172 }173 canvas.axes(174 &right,175 &[1.4, 1.6, 1.8, 2.0, 2.2, 2.4, 2.6, 2.8],176 &[1.0, 1.4, 1.8, 2.2, 2.6],177 );178 let start = right.x.0.max(right.y.0);179 let stop = right.x.1.min(right.y.1);180 let (dx0, dy0) = right.place(start, start);181 let (dx1, dy1) = right.place(stop, stop);182 canvas.line(dx0, dy0, dx1, dy1, GREY, 1.5);183 for (df, dw) in series.fractal.iter().zip(&series.walker) {184 let (x, y) = right.place(*df, 2.0 * df / dw);185 canvas.disc(x, y, 7.0, NAVY);186 }187 let gasket = right.place(3f64.ln() / 2f64.ln(), 2.0 * 3f64.ln() / 5f64.ln());188 canvas.star(gasket.0, gasket.1, 13.0, RUST);189 canvas.pixels190}191192pub fn write(path: &Path, series: &Series) {193 let bytes = png(&render(series), WIDTH, HEIGHT, 1).expect("the figure encodes");194 io::write(path, &bytes).expect("the figure writes");195}