kahlo 0.0.3

Optimized software rendering library.
Documentation
use rand::{
    distr::{uniform::SampleUniform, Distribution, Uniform},
    prelude::*,
    Fill,
};
use std::ops::Range;

use crate::{
    colour::{BlendMode, Colour},
    formats::{self, PixelFormatSpec, A8},
    math::{PixelBox, PixelPoint, PixelSize},
    prelude::*,
    Bitmap,
};

use super::{Context, ContextValueTag};

pub struct Benchmark;
impl ContextValueTag for Benchmark {
    type Value = bool;
}

const DEFAULT_SIZE_BOUNDS: Range<i32> = 4..8;
pub struct ImageSizeBounds;
impl ContextValueTag for ImageSizeBounds {
    type Value = Range<i32>;
}

pub struct DstSize;
impl ContextValueTag for DstSize {
    type Value = PixelSize;
}

pub struct SrcSize;
impl ContextValueTag for SrcSize {
    type Value = PixelSize;
}

pub struct AlphamapSize;
impl ContextValueTag for AlphamapSize {
    type Value = PixelSize;
}

impl ContextValueTag for BlendMode {
    type Value = BlendMode;
}

fn point_in_size(rng: &mut dyn Rng, sz: &PixelSize) -> PixelPoint {
    let xdist = Uniform::new(0, sz.width).unwrap();
    let ydist = Uniform::new(0, sz.height).unwrap();
    PixelPoint::new(xdist.sample(rng), ydist.sample(rng))
}

pub fn constant<V: Clone>(v: V) -> impl Fn(&mut Context) -> Option<V> {
    move |_| Some(v.clone())
}

pub fn uniform<V: SampleUniform + Clone>(from: V, to: V) -> impl Fn(&mut Context) -> Option<V> {
    move |ctx| {
        let uniform = Uniform::new(from.clone(), to.clone()).unwrap();
        Some(ctx.rng().sample(uniform))
    }
}

pub fn random_colour(ctx: &mut Context) -> Option<Colour> {
    let rng = ctx.rng();
    Some(Colour {
        r: rng.random(),
        g: rng.random(),
        b: rng.random(),
        a: rng.random(),
    })
}

pub fn blend_mode(ctx: &mut Context) -> Option<BlendMode> {
    if let Some(v) = ctx.lookup::<BlendMode>() {
        return Some(*v);
    }
    let rng = ctx.rng();
    let dist = Uniform::new(0, 5).unwrap();
    Some(match rng.sample(dist) {
        0 => BlendMode::SourceCopy,
        1 => BlendMode::Simple,
        2 => BlendMode::SourceAlpha,
        3 => BlendMode::BackdropAlpha,
        4 => BlendMode::Multiply,
        _ => unreachable!(),
    })
}

pub fn dst_image_generator<Format: PixelFormatSpec>(ctx: &mut Context) -> Option<Bitmap<Format>> {
    let size_bounds = ctx
        .lookup::<ImageSizeBounds>()
        .unwrap_or(&DEFAULT_SIZE_BOUNDS);
    let size_dist = Uniform::new(size_bounds.start, size_bounds.end).unwrap();
    let mut bitmap = Bitmap::new(
        size_dist.sample(ctx.rng()) as usize,
        size_dist.sample(ctx.rng()) as usize,
    );
    u8::fill_slice(bitmap.data_mut(), ctx.rng());
    ctx.insert::<DstSize>(bitmap.size());
    Some(bitmap)
}

pub fn src_image_generator<Format: PixelFormatSpec>(ctx: &mut Context) -> Option<Bitmap<Format>> {
    let size_bounds = ctx
        .lookup::<ImageSizeBounds>()
        .unwrap_or(&DEFAULT_SIZE_BOUNDS);
    let size_dist = Uniform::new(size_bounds.start, size_bounds.end).unwrap();
    let mut bitmap = Bitmap::new(
        size_dist.sample(ctx.rng()) as usize,
        size_dist.sample(ctx.rng()) as usize,
    );
    u8::fill_slice(bitmap.data_mut(), ctx.rng());
    ctx.insert::<SrcSize>(bitmap.size());
    Some(bitmap)
}

pub fn alphamap<'l>(ctx: &mut Context) -> Option<&'l dyn BitmapAccess<formats::A8>> {
    let size = if *ctx.lookup::<Benchmark>().unwrap_or(&false) {
        *ctx.lookup::<DstSize>().unwrap()
    } else {
        let size_bounds = ctx
            .lookup::<ImageSizeBounds>()
            .unwrap_or(&DEFAULT_SIZE_BOUNDS);
        let size_dist = Uniform::new(size_bounds.start, size_bounds.end).unwrap();
        PixelSize::new(
            size_dist.sample(ctx.rng()) as i32,
            size_dist.sample(ctx.rng()) as i32,
        )
    };

    let mut alphamap = Bitmap::<A8>::new(size.width as usize, size.height as usize);
    u8::fill_slice(alphamap.data_mut(), ctx.rng());
    ctx.insert::<AlphamapSize>(alphamap.size());
    // since this is a test context, we can actually just leak the alphamap to deal with lifetime
    // stuff.
    let alphamap = Box::leak(Box::new(alphamap));
    Some(alphamap)
}

pub fn alphamap_area(ctx: &mut Context) -> Option<PixelBox> {
    if *ctx.lookup::<Benchmark>().unwrap_or(&false) {
        return Some(PixelBox::from_size(*ctx.lookup::<DstSize>().unwrap()));
    }
    let dst_size = *ctx.lookup::<DstSize>()?;
    let p1 = point_in_size(ctx.rng(), &dst_size);
    let p2 = point_in_size(ctx.rng(), &dst_size);
    Some(PixelBox::new(p1.min(p2), p1.max(p2)))
}

pub fn dst_point(ctx: &mut Context) -> Option<PixelPoint> {
    if *ctx.lookup::<Benchmark>().unwrap_or(&false) {
        return Some(PixelPoint::zero());
    }
    let dstsize = *ctx.lookup::<DstSize>()?;
    Some(point_in_size(ctx.rng(), &dstsize))
}

pub fn dst_area(ctx: &mut Context) -> Option<PixelBox> {
    if *ctx.lookup::<Benchmark>().unwrap_or(&false) {
        return Some(PixelBox::from_size(*ctx.lookup::<DstSize>().unwrap()));
    }
    let dst_size = *ctx.lookup::<DstSize>()?;
    let p1 = point_in_size(ctx.rng(), &dst_size);
    let p2 = point_in_size(ctx.rng(), &dst_size);
    Some(PixelBox::new(p1.min(p2), p1.max(p2)))
}

pub fn src_area(ctx: &mut Context) -> Option<PixelBox> {
    if *ctx.lookup::<Benchmark>().unwrap_or(&false) {
        return Some(PixelBox::from_size(*ctx.lookup::<SrcSize>().unwrap()));
    }
    let src_size = *ctx.lookup::<SrcSize>()?;
    let p1 = point_in_size(ctx.rng(), &src_size);
    let p2 = point_in_size(ctx.rng(), &src_size);
    Some(PixelBox::new(p1.min(p2), p1.max(p2)))
}