purrmitive 0.2.0

Reproducing images with geometric primitives.
Documentation
use crate::clamp;
use crate::core::PurrShape;
use crate::graphics::raster::rasterize_quad_bezier;
use crate::graphics::{Point, Scanline, Shape};
use crate::{Rgba, RgbaImage};
use rand::{Rng, RngCore, SeedableRng};
use rand_distr::StandardNormal;

#[derive(Debug, Clone, Copy)]
pub struct Quadratic {
    pub p0: Point,
    pub p1: Point,
    pub p2: Point,
}

impl Default for Quadratic {
    fn default() -> Self {
        Quadratic {
            p0: Point { x: 0, y: 0 },
            p1: Point { x: 0, y: 0 },
            p2: Point { x: 0, y: 0 },
        }
    }
}

impl Quadratic {
    pub fn valid(&self) -> bool {
        let dx01 = self.p0.x - self.p1.x;
        let dy01 = self.p0.y - self.p1.y;
        let dx12 = self.p1.x - self.p2.x;
        let dy12 = self.p1.y - self.p2.y;
        let dx02 = self.p0.x - self.p2.x;
        let dy02 = self.p0.y - self.p2.y;
        let d01 = dx01 * dx01 + dy01 * dy01;
        let d12 = dx12 * dx12 + dy12 * dy12;
        let d02 = dx02 * dx02 + dy02 * dy02;
        d02 > d01 && d02 > d12
    }
}

impl Shape for Quadratic {
    fn random<T: SeedableRng + RngCore>(w: u32, h: u32, rng: &mut T) -> Self {
        let px = rng.gen_range(0, w as i32);
        let py = rng.gen_range(0, h as i32);
        let p0 = Point { x: px, y: py };
        let p1 = Point {
            x: px + rng.gen_range(-20, 20),
            y: py + rng.gen_range(-20, 20),
        };
        let p2 = Point {
            x: px + rng.gen_range(-20, 20),
            y: py + rng.gen_range(-20, 20),
        };
        let mut q = Quadratic { p0, p1, p2 };
        q.mutate(w, h, rng);
        q
    }

    fn mutate<T: SeedableRng + RngCore>(&mut self, w: u32, h: u32, rng: &mut T) {
        loop {
            match rng.gen_range(0, 2) {
                0 => {
                    self.p0.x = clamp(
                        self.p0.x + (16.0 * rng.sample::<f64, _>(StandardNormal)) as i32,
                        0,
                        w as i32 - 1,
                    );
                    self.p0.y = clamp(
                        self.p0.y + (16.0 * rng.sample::<f64, _>(StandardNormal)) as i32,
                        0,
                        h as i32 - 1,
                    );
                }
                1 => {
                    self.p1.x = clamp(
                        self.p1.x + (16.0 * rng.sample::<f64, _>(StandardNormal)) as i32,
                        0,
                        w as i32 - 1,
                    );
                    self.p1.y = clamp(
                        self.p1.y + (16.0 * rng.sample::<f64, _>(StandardNormal)) as i32,
                        0,
                        h as i32 - 1,
                    );
                }
                2 => {
                    self.p2.x = clamp(
                        self.p2.x + (16.0 * rng.sample::<f64, _>(StandardNormal)) as i32,
                        0,
                        w as i32 - 1,
                    );
                    self.p2.y = clamp(
                        self.p2.y + (16.0 * rng.sample::<f64, _>(StandardNormal)) as i32,
                        0,
                        h as i32 - 1,
                    );
                }
                _ => unreachable!(),
            }
            if self.valid() {
                break;
            }
        }
    }

    fn rasterize(&self, w: u32, h: u32) -> Vec<Scanline> {
        let lines = rasterize_quadratic(self, w, h);
        let mut visible_lines: Vec<Scanline> = lines
            .into_iter()
            .filter(|l| l.x1 <= l.x2 && l.x2 > 0 && l.x1 < w && l.y > 0 && l.y < h)
            .collect();
        for line in &mut visible_lines {
            line.crop(w, h);
        }
        visible_lines
    }

    fn draw(&self, img: &mut RgbaImage, color: &Rgba<u8>) {
        let (w, h) = img.dimensions();
        let lines = self.rasterize(w, h);
        for line in lines {
            line.draw(img, &color);
        }
    }

    fn to_svg(&self, attr: &str) -> String {
        let attr = attr.replace("fill", "stroke");
        format!(
            "<path {} fill=\"none\" d=\"M {} {} Q {} {}, {} {}\" stroke-width=\"{}\" />",
            attr, self.p0.x, self.p0.y, self.p1.x, self.p1.y, self.p2.x, self.p2.y, 0.5
        )
    }
}

fn rasterize_quadratic(q: &Quadratic, w: u32, h: u32) -> Vec<Scanline> {
    let mut ymin = std::i32::MAX;
    let mut ymax = std::i32::MIN;
    for p in &[q.p0, q.p1, q.p2] {
        if p.y < ymin {
            ymin = p.y;
        }
        if p.y > ymax {
            ymax = p.y;
        }
    }
    let range = (ymax - ymin) as usize;

    let mut buf_lhs: Vec<i32> = vec![std::i32::MAX; range + 2];
    let mut buf_rhs: Vec<i32> = vec![std::i32::MIN; range + 2];
    let mut scanlines = Vec::new();

    let x0 = q.p0.x;
    let y0 = q.p0.y;
    let x1 = q.p1.x;
    let y1 = q.p1.y;
    let x2 = q.p2.x;
    let y2 = q.p2.y;

    rasterize_quad_bezier(
        x0,
        y0,
        x1,
        y1,
        x2,
        y2,
        &mut buf_lhs,
        &mut buf_rhs,
        &mut scanlines,
        w,
        h,
        ymin,
    );
    for i in 0..range {
        let y = i as i32 + ymin;
        if y >= 0 && y < h as i32 {
            if buf_lhs[i] >= 0 {
                scanlines.push(Scanline {
                    y: clamp(y as u32, 0, h - 1),
                    x1: clamp(buf_lhs[i], 0, w as i32 - 1) as u32,
                    x2: clamp(buf_rhs[i], 0, w as i32 - 1) as u32,
                });
            }
        }
    }
    scanlines
}

impl PurrShape for Quadratic {}