kahlo 0.0.3

Optimized software rendering library.
Documentation
use rand::Fill;

use crate::bitmap::BitmapAccess;
use crate::ext::BitmapExt;
use crate::formats::{self, PixelFormatSpec, A8};
use crate::math;

use super::composite::CompositeOp;
use super::copy_from::CopyFromOp;
use super::fill::FillOp;
use super::fill_region::FillRegionOp;
use super::fill_region_masked::FillRegionMaskedOp;
use super::generate::{self, BinaryInputSpec, UnaryInputSpec, ValueGeneratorList};
use super::{GenericBinary, GenericUnary};

fn compare_images<SrcFormat: PixelFormatSpec, DstFormat: PixelFormatSpec>(
    original_dst_image: &impl BitmapAccess<DstFormat>,
    original_src_image: Option<&dyn BitmapAccess<SrcFormat>>,
    specific: &impl BitmapAccess<DstFormat>,
    generic: &impl BitmapAccess<DstFormat>,
    params: &dyn std::fmt::Debug,
) {
    let mut flag = false;
    // compare outputs in a format-aware fashion
    for y in 0..specific.height() {
        for x in 0..specific.width() {
            let scol = specific.read_colour(math::PixelPoint::new(x as i32, y as i32));
            let gcol = generic.read_colour(math::PixelPoint::new(x as i32, y as i32));
            if scol != gcol {
                flag = true;
                println!("Pixel mismatch at {x},{y}: {scol:?} vs {gcol:?}");
            }
        }
    }

    if flag {
        let mut path = std::path::PathBuf::new();
        path.push(env!("CARGO_MANIFEST_DIR"));
        let random_id = {
            let mut data = [0u8; 6];
            u8::fill_slice(&mut data, &mut rand::rng());
            hex::encode(data)
        };
        path.push(format!("test_failure.{random_id}"));
        println!("image mismatch");
        std::fs::write(
            &path,
            format!(
                concat!(
                    "dst: {dst_img:?}\n",
                    "src: {src_img:?}\n",
                    "params: {params:?}\n",
                    "dst data: ({dst_fmt}) {dst_data}\n",
                    "src data: ({src_fmt}) {src_data}\n",
                    "generic output: {generic_data}\n",
                    "specific output: {specific_data}\n",
                ),
                dst_img = original_dst_image,
                src_img = original_src_image,
                dst_fmt = DstFormat::NAME,
                src_fmt = SrcFormat::NAME,
                dst_data = hex::encode(original_dst_image.data()),
                src_data = original_src_image
                    .map(|img| hex::encode(img.data()))
                    .unwrap_or_else(|| "no src image".to_string()),
                generic_data = hex::encode(generic.data()),
                specific_data = hex::encode(specific.data()),
                params = params,
            )
            .as_str(),
        )
        .unwrap();
        println!("details written to path: {}", path.display());

        panic!("Test case failed!")
    }
}

fn test_unary_format<UIS: UnaryInputSpec, Format: PixelFormatSpec>(n: usize) {
    let gen = UIS::gen();

    let op = UIS::build();
    if op.is_generic::<Format>() {
        println!("Skipping generic implementation {}", Format::NAME);
        return;
    } else {
        println!("Testing specific implementation {}", Format::NAME);
    }

    for i in 0..n {
        let mut ctx = generate::Context::new(i);
        let dst_img =
            generate::dst_image_generator::<Format>(&mut ctx).expect("could not generate image");
        let params = gen
            .generate(&mut ctx)
            .expect("could not generate parameters");
        println!("\trunning test {i}");
        let mut generic_img = dst_img.clone();
        let mut specific_img = dst_img.clone();

        op.select::<Format>()(&mut specific_img, &params);
        <UIS as GenericUnary<UIS::Params<'static>>>::perform::<Format>(&mut generic_img, &params);

        compare_images::<A8, Format>(&dst_img, None, &specific_img, &generic_img, &params);
    }
}

fn test_unary<UIS: UnaryInputSpec>(n: usize) {
    test_unary_format::<UIS, formats::A8>(n);
    test_unary_format::<UIS, formats::Abgr32>(n);
    test_unary_format::<UIS, formats::Rgba32>(n);
    // test_unary_format::<UIS, formats::Rgba32P>(n);
}

fn test_binary_format<
    BIS: BinaryInputSpec,
    DstFormat: PixelFormatSpec,
    SrcFormat: PixelFormatSpec,
>(
    n: usize,
) {
    let gen = BIS::gen();

    let op = BIS::build();
    if op.is_generic::<DstFormat, SrcFormat>() {
        println!(
            "Skipping generic implementation ({},{})",
            DstFormat::NAME,
            SrcFormat::NAME
        );
        return;
    } else {
        println!(
            "Testing specific implementation ({},{})",
            DstFormat::NAME,
            SrcFormat::NAME
        );
    }

    for i in 0..n {
        let mut ctx = generate::Context::new(i);
        let dst_img =
            generate::dst_image_generator::<DstFormat>(&mut ctx).expect("could not generate image");
        let src_img =
            generate::src_image_generator::<SrcFormat>(&mut ctx).expect("could not generate image");
        let mut specific_dst_img = dst_img.clone();
        let mut generic_dst_img = dst_img.clone();

        let params = gen
            .generate(&mut ctx)
            .expect("could not generate parameters");
        println!("\trunning test {i}");

        op.select::<DstFormat, SrcFormat>()(&mut specific_dst_img, &src_img, &params);
        <BIS as GenericBinary<BIS::Params<'static>>>::perform::<DstFormat, SrcFormat>(
            &mut generic_dst_img,
            &src_img,
            &params,
        );

        compare_images(
            &dst_img,
            Some(&src_img),
            &specific_dst_img,
            &generic_dst_img,
            &params,
        );
    }
}

fn test_binary<BIS: BinaryInputSpec>(n: usize) {
    test_binary_format::<BIS, formats::A8, formats::A8>(n);
    test_binary_format::<BIS, formats::A8, formats::Abgr32>(n);
    test_binary_format::<BIS, formats::A8, formats::Rgba32>(n);
    // test_binary_format::<BIS, formats::A8, formats::Rgba32P>(n);

    test_binary_format::<BIS, formats::Abgr32, formats::A8>(n);
    test_binary_format::<BIS, formats::Abgr32, formats::Abgr32>(n);
    test_binary_format::<BIS, formats::Abgr32, formats::Rgba32>(n);
    // test_binary_format::<BIS, formats::Abgr32, formats::Rgba32P>(n);

    test_binary_format::<BIS, formats::Rgba32, formats::A8>(n);
    test_binary_format::<BIS, formats::Rgba32, formats::Abgr32>(n);
    test_binary_format::<BIS, formats::Rgba32, formats::Rgba32>(n);
    // test_binary_format::<BIS, formats::Rgba32, formats::Rgba32P>(n);

    /*test_binary_format::<BIS, formats::Rgba32P, formats::A8>(n);
    test_binary_format::<BIS, formats::Rgba32P, formats::Abgr32>(n);
    test_binary_format::<BIS, formats::Rgba32P, formats::Rgba32>(n);
    test_binary_format::<BIS, formats::Rgba32P, formats::Rgba32P>(n);*/
}

#[test]
fn fill_op() {
    test_unary::<FillOp>(100);
}

#[test]
fn fill_region_op() {
    test_unary::<FillRegionOp>(100);
}

#[test]
fn fill_region_masked_op() {
    test_unary::<FillRegionMaskedOp>(40);
}

#[test]
fn copy_from_op() {
    test_binary::<CopyFromOp>(20);
}

#[test]
fn composite_op() {
    test_binary::<CompositeOp>(40);
}