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}