agent-image-diff 0.2.4

Structured image diff with JSON output for agent workflows
Documentation
use image::RgbaImage;

use crate::color;

/// Check if a pixel at (x, y) is anti-aliased by examining its neighbors.
///
/// A pixel is considered anti-aliased if:
/// 1. It has fewer than 3 identical neighbors (not part of a solid region)
/// 2. It sits between a darker and brighter neighbor (gradient transition)
/// 3. The darkest/brightest neighbors have >= 3 identical neighbors in the other image
///
/// Based on Vysniauskas (2009) as adapted by pixelmatch.
pub fn is_antialiased(
    img: &RgbaImage,
    other: &RgbaImage,
    x: u32,
    y: u32,
) -> bool {
    let (width, height) = img.dimensions();
    let center = img.get_pixel(x, y).0;

    let mut identical_count = 0;
    let mut min_y_delta = 0.0_f64;
    let mut max_y_delta = 0.0_f64;
    let mut min_x = x;
    let mut min_y_coord = y;
    let mut max_x = x;
    let mut max_y_coord = y;

    // Examine 8 neighbors
    for dy in -1i32..=1 {
        for dx in -1i32..=1 {
            if dx == 0 && dy == 0 {
                continue;
            }
            let nx = x as i32 + dx;
            let ny = y as i32 + dy;
            if nx < 0 || ny < 0 || nx >= width as i32 || ny >= height as i32 {
                continue;
            }
            let nx = nx as u32;
            let ny = ny as u32;
            let neighbor = img.get_pixel(nx, ny).0;

            // Check if identical
            if center == neighbor {
                identical_count += 1;
                if identical_count >= 3 {
                    return false; // Part of a solid region, not AA
                }
                continue;
            }

            // Compute luminance delta (Y-only)
            let y_delta = color::color_delta(center, neighbor, true);

            if y_delta < min_y_delta {
                min_y_delta = y_delta;
                min_x = nx;
                min_y_coord = ny;
            }
            if y_delta > max_y_delta {
                max_y_delta = y_delta;
                max_x = nx;
                max_y_coord = ny;
            }
        }
    }

    // Must have both darker and brighter neighbors
    if min_y_delta >= 0.0 || max_y_delta <= 0.0 {
        return false;
    }

    // Check if the darkest and brightest neighbors are solid in the other image
    has_many_identical_neighbors(other, min_x, min_y_coord)
        && has_many_identical_neighbors(other, max_x, max_y_coord)
}

/// Check if a pixel has >= 3 identical neighbors in the given image.
fn has_many_identical_neighbors(img: &RgbaImage, x: u32, y: u32) -> bool {
    let (width, height) = img.dimensions();
    if x >= width || y >= height {
        return false;
    }
    let center = img.get_pixel(x, y).0;
    let mut count = 0;

    for dy in -1i32..=1 {
        for dx in -1i32..=1 {
            if dx == 0 && dy == 0 {
                continue;
            }
            let nx = x as i32 + dx;
            let ny = y as i32 + dy;
            if nx < 0 || ny < 0 || nx >= width as i32 || ny >= height as i32 {
                continue;
            }
            if img.get_pixel(nx as u32, ny as u32).0 == center {
                count += 1;
                if count >= 3 {
                    return true;
                }
            }
        }
    }
    false
}