aboutsummaryrefslogtreecommitdiff
path: root/crates/render-parity/src/lib.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/render-parity/src/lib.rs')
-rw-r--r--crates/render-parity/src/lib.rs212
1 files changed, 0 insertions, 212 deletions
diff --git a/crates/render-parity/src/lib.rs b/crates/render-parity/src/lib.rs
deleted file mode 100644
index cb412e9..0000000
--- a/crates/render-parity/src/lib.rs
+++ /dev/null
@@ -1,212 +0,0 @@
-use image::{ImageBuffer, Rgba, RgbaImage};
-use serde::Deserialize;
-
-#[derive(Debug, Clone, Deserialize, Default)]
-pub struct ManifestMeta {
- pub width: Option<u32>,
- pub height: Option<u32>,
- pub lod: Option<usize>,
- pub group: Option<usize>,
- pub angle: Option<f32>,
- pub diff_threshold: Option<u8>,
- pub max_mean_abs: Option<f32>,
- pub max_changed_ratio: Option<f32>,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-pub struct CaseSpec {
- pub id: String,
- pub archive: String,
- pub model: Option<String>,
- pub reference: String,
- pub width: Option<u32>,
- pub height: Option<u32>,
- pub lod: Option<usize>,
- pub group: Option<usize>,
- pub angle: Option<f32>,
- pub diff_threshold: Option<u8>,
- pub max_mean_abs: Option<f32>,
- pub max_changed_ratio: Option<f32>,
-}
-
-#[derive(Debug, Clone, Deserialize)]
-pub struct ParityManifest {
- #[serde(default)]
- pub meta: ManifestMeta,
- #[serde(rename = "case", default)]
- pub cases: Vec<CaseSpec>,
-}
-
-#[derive(Debug, Clone)]
-pub struct DiffMetrics {
- pub width: u32,
- pub height: u32,
- pub mean_abs: f32,
- pub max_abs: u8,
- pub changed_pixels: u64,
- pub changed_ratio: f32,
-}
-
-pub fn compare_images(
- reference: &RgbaImage,
- actual: &RgbaImage,
- diff_threshold: u8,
-) -> Result<DiffMetrics, String> {
- let (rw, rh) = reference.dimensions();
- let (aw, ah) = actual.dimensions();
- if rw != aw || rh != ah {
- return Err(format!(
- "image size mismatch: reference={}x{}, actual={}x{}",
- rw, rh, aw, ah
- ));
- }
-
- let mut diff_sum = 0u64;
- let mut max_abs = 0u8;
- let mut changed_pixels = 0u64;
- let pixel_count = u64::from(rw).saturating_mul(u64::from(rh));
-
- for (ref_px, act_px) in reference.pixels().zip(actual.pixels()) {
- let mut pixel_changed = false;
- for chan in 0..3 {
- let a = i16::from(ref_px[chan]);
- let b = i16::from(act_px[chan]);
- let diff = (a - b).unsigned_abs() as u8;
- diff_sum = diff_sum.saturating_add(u64::from(diff));
- if diff > max_abs {
- max_abs = diff;
- }
- if diff > diff_threshold {
- pixel_changed = true;
- }
- }
- if pixel_changed {
- changed_pixels = changed_pixels.saturating_add(1);
- }
- }
-
- let channels = pixel_count.saturating_mul(3);
- let mean_abs = if channels == 0 {
- 0.0
- } else {
- diff_sum as f32 / channels as f32
- };
- let changed_ratio = if pixel_count == 0 {
- 0.0
- } else {
- changed_pixels as f32 / pixel_count as f32
- };
-
- Ok(DiffMetrics {
- width: rw,
- height: rh,
- mean_abs,
- max_abs,
- changed_pixels,
- changed_ratio,
- })
-}
-
-pub fn build_diff_image(reference: &RgbaImage, actual: &RgbaImage) -> Result<RgbaImage, String> {
- let (rw, rh) = reference.dimensions();
- let (aw, ah) = actual.dimensions();
- if rw != aw || rh != ah {
- return Err(format!(
- "image size mismatch: reference={}x{}, actual={}x{}",
- rw, rh, aw, ah
- ));
- }
-
- let mut out: ImageBuffer<Rgba<u8>, Vec<u8>> = ImageBuffer::new(rw, rh);
- for (dst, (ref_px, act_px)) in out
- .pixels_mut()
- .zip(reference.pixels().zip(actual.pixels()))
- {
- let dr = (i16::from(ref_px[0]) - i16::from(act_px[0])).unsigned_abs() as u8;
- let dg = (i16::from(ref_px[1]) - i16::from(act_px[1])).unsigned_abs() as u8;
- let db = (i16::from(ref_px[2]) - i16::from(act_px[2])).unsigned_abs() as u8;
- *dst = Rgba([dr, dg, db, 255]);
- }
- Ok(out)
-}
-
-pub fn evaluate_metrics(
- metrics: &DiffMetrics,
- max_mean_abs: f32,
- max_changed_ratio: f32,
-) -> Vec<String> {
- let mut violations = Vec::new();
- if metrics.mean_abs > max_mean_abs {
- violations.push(format!(
- "mean_abs {:.4} > allowed {:.4}",
- metrics.mean_abs, max_mean_abs
- ));
- }
- if metrics.changed_ratio > max_changed_ratio {
- violations.push(format!(
- "changed_ratio {:.4}% > allowed {:.4}%",
- metrics.changed_ratio * 100.0,
- max_changed_ratio * 100.0
- ));
- }
- violations
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- fn solid(w: u32, h: u32, r: u8, g: u8, b: u8) -> RgbaImage {
- let mut img = RgbaImage::new(w, h);
- for px in img.pixels_mut() {
- *px = Rgba([r, g, b, 255]);
- }
- img
- }
-
- #[test]
- fn compare_identical_images() {
- let ref_img = solid(4, 3, 10, 20, 30);
- let act_img = solid(4, 3, 10, 20, 30);
- let metrics = compare_images(&ref_img, &act_img, 2).expect("comparison must succeed");
- assert_eq!(metrics.width, 4);
- assert_eq!(metrics.height, 3);
- assert_eq!(metrics.max_abs, 0);
- assert_eq!(metrics.changed_pixels, 0);
- assert_eq!(metrics.mean_abs, 0.0);
- assert_eq!(metrics.changed_ratio, 0.0);
- }
-
- #[test]
- fn compare_detects_changes_and_thresholds() {
- let mut ref_img = solid(2, 2, 100, 100, 100);
- let mut act_img = solid(2, 2, 100, 100, 100);
- ref_img.put_pixel(1, 1, Rgba([120, 100, 100, 255]));
- act_img.put_pixel(1, 1, Rgba([100, 100, 100, 255]));
-
- let metrics = compare_images(&ref_img, &act_img, 5).expect("comparison must succeed");
- assert_eq!(metrics.max_abs, 20);
- assert_eq!(metrics.changed_pixels, 1);
- assert!((metrics.changed_ratio - 0.25).abs() < 1e-6);
- assert!(metrics.mean_abs > 0.0);
-
- let violations = evaluate_metrics(&metrics, 2.0, 0.20);
- assert_eq!(violations.len(), 1);
- assert!(violations[0].contains("changed_ratio"));
- }
-
- #[test]
- fn build_diff_image_returns_per_channel_abs_diff() {
- let mut ref_img = solid(1, 1, 100, 150, 200);
- let mut act_img = solid(1, 1, 90, 180, 170);
- ref_img.put_pixel(0, 0, Rgba([100, 150, 200, 255]));
- act_img.put_pixel(0, 0, Rgba([90, 180, 170, 255]));
-
- let diff = build_diff_image(&ref_img, &act_img).expect("diff image must build");
- let px = diff.get_pixel(0, 0);
- assert_eq!(px[0], 10);
- assert_eq!(px[1], 30);
- assert_eq!(px[2], 30);
- assert_eq!(px[3], 255);
- }
-}