semdiff-differ-image 0.4.2

Image diff calculator and reporters for semdiff.
Documentation
use crate::{ImageDiff, ImageDiffReporter, image_format};
use image::ImageError;
use semdiff_core::fs::FileLeaf;
use semdiff_core::{DetailReporter, MayUnsupported};
use semdiff_output::json::JsonReport;
use serde::Serialize;
use thiserror::Error;

const COMPARES_NAME: &str = "image";

#[derive(Debug, Error)]
pub enum ImageJsonReportError {
    #[error("image decode error: {0}")]
    ImageDecode(#[from] ImageError),
}

impl<W> DetailReporter<ImageDiff, FileLeaf, JsonReport<W>> for ImageDiffReporter {
    type Error = ImageJsonReportError;

    fn report_unchanged(
        &self,
        name: &str,
        _diff: &ImageDiff,
        reporter: &JsonReport<W>,
    ) -> Result<MayUnsupported<()>, Self::Error> {
        reporter.record_unchanged(name, COMPARES_NAME, ());
        Ok(MayUnsupported::Ok(()))
    }

    fn report_modified(
        &self,
        name: &str,
        diff: &ImageDiff,
        reporter: &JsonReport<W>,
    ) -> Result<MayUnsupported<()>, Self::Error> {
        let report = ModifiedReport {
            expected_width: diff.expected().width,
            expected_height: diff.expected().height,
            actual_width: diff.actual().width,
            actual_height: diff.actual().height,
            diff_pixels: diff.diff_stat().diff_pixels,
        };
        reporter.record_modified(name, COMPARES_NAME, report);
        Ok(MayUnsupported::Ok(()))
    }

    fn report_added(
        &self,
        name: &str,
        data: &FileLeaf,
        reporter: &JsonReport<W>,
    ) -> Result<MayUnsupported<()>, Self::Error> {
        let Some(format) = image_format(&data.kind) else {
            return Ok(MayUnsupported::Unsupported);
        };
        let image = image::load_from_memory_with_format(&data.content, format)?;
        let report = SingleReport {
            width: image.width(),
            height: image.height(),
        };
        reporter.record_added(name, COMPARES_NAME, report);
        Ok(MayUnsupported::Ok(()))
    }

    fn report_deleted(
        &self,
        name: &str,
        data: &FileLeaf,
        reporter: &JsonReport<W>,
    ) -> Result<MayUnsupported<()>, Self::Error> {
        let Some(format) = image_format(&data.kind) else {
            return Ok(MayUnsupported::Unsupported);
        };
        let image = image::load_from_memory_with_format(&data.content, format)?;
        let report = SingleReport {
            width: image.width(),
            height: image.height(),
        };
        reporter.record_deleted(name, COMPARES_NAME, report);
        Ok(MayUnsupported::Ok(()))
    }
}

#[derive(Serialize)]
struct ModifiedReport {
    expected_width: u32,
    expected_height: u32,
    actual_width: u32,
    actual_height: u32,
    diff_pixels: u64,
}

#[derive(Serialize)]
struct SingleReport {
    width: u32,
    height: u32,
}