1pub mod antialias;
2pub mod classify;
3pub mod cli;
4pub mod cluster;
5pub mod color;
6pub mod compare;
7pub mod denoise;
8pub mod dilate;
9pub mod merge;
10pub mod output;
11pub mod region;
12
13pub fn diff_images(
15 baseline: &image::RgbaImage,
16 candidate: &image::RgbaImage,
17 options: DiffOptions,
18) -> region::DiffResult {
19 let mut compare_result = compare::compare(
20 baseline,
21 candidate,
22 &compare::CompareOptions {
23 threshold: options.threshold,
24 detect_antialias: options.detect_antialias,
25 },
26 );
27
28 denoise::denoise(
29 &mut compare_result.diff_mask,
30 compare_result.width,
31 compare_result.height,
32 options.denoise,
33 );
34
35 let dilated_mask = dilate::dilate(
36 &compare_result.diff_mask,
37 compare_result.width,
38 compare_result.height,
39 options.dilate,
40 );
41
42 let labels = cluster::label_components(
43 &dilated_mask,
44 compare_result.width,
45 compare_result.height,
46 options.connectivity,
47 );
48
49 let mut regions = region::extract_regions(
50 &labels,
51 &compare_result.delta_map,
52 compare_result.width,
53 compare_result.height,
54 options.min_region_size,
55 );
56
57 merge::merge_regions(&mut regions, options.merge_distance);
58
59 classify::classify_regions(
60 &mut regions,
61 baseline,
62 candidate,
63 &compare_result.diff_mask,
64 compare_result.width,
65 compare_result.height,
66 );
67
68 let total_pixels = compare_result.width as u64 * compare_result.height as u64;
69 let changed_pixels = compare_result.diff_mask.iter().filter(|&&v| v).count() as u64;
70
71 let dimension_mismatch =
72 if baseline.width() != candidate.width() || baseline.height() != candidate.height() {
73 Some(region::DimensionMismatch {
74 baseline: region::Dimensions {
75 width: baseline.width(),
76 height: baseline.height(),
77 },
78 candidate: region::Dimensions {
79 width: candidate.width(),
80 height: candidate.height(),
81 },
82 })
83 } else {
84 None
85 };
86
87 region::DiffResult {
88 dimensions: region::Dimensions {
89 width: compare_result.width,
90 height: compare_result.height,
91 },
92 stats: region::DiffStats {
93 changed_pixels,
94 total_pixels,
95 diff_percentage: if total_pixels > 0 {
96 (changed_pixels as f64 / total_pixels as f64) * 100.0
97 } else {
98 0.0
99 },
100 region_count: regions.len(),
101 antialiased_pixels: compare_result.aa_pixel_count,
102 },
103 is_match: regions.is_empty(),
104 regions,
105 dimension_mismatch,
106 }
107}
108
109pub struct DiffOptions {
110 pub threshold: f64,
111 pub detect_antialias: bool,
112 pub connectivity: u8,
113 pub min_region_size: u32,
114 pub denoise: u32,
115 pub dilate: u32,
116 pub merge_distance: u32,
117}
118
119impl Default for DiffOptions {
120 fn default() -> Self {
121 Self {
122 threshold: 0.1,
123 detect_antialias: true,
124 connectivity: 8,
125 min_region_size: 25,
126 denoise: 25,
127 dilate: 0,
128 merge_distance: 50,
129 }
130 }
131}