11use crate :: environment:: RenderInterface ;
22use crate :: options:: image_comparison:: ImageComparison ;
33use crate :: util:: { read_bytes, write_bytes} ;
4- use anyhow:: anyhow;
4+ use anyhow:: { Context as _ , anyhow} ;
55use image:: { EncodableLayout , ImageBuffer , ImageFormat , Pixel , PixelWithColorType } ;
66use ruffle_core:: Player ;
7+ use std:: borrow:: Cow ;
78use std:: io:: Cursor ;
89use std:: ops:: Deref ;
910use std:: sync:: { Arc , Mutex } ;
@@ -12,12 +13,10 @@ use vfs::VfsPath;
1213pub fn capture_and_compare_image (
1314 base_path : & VfsPath ,
1415 player : & Arc < Mutex < Player > > ,
15- name : & String ,
16+ name : & str ,
1617 image_comparison : ImageComparison ,
1718 render_interface : Option < & dyn RenderInterface > ,
1819) -> anyhow:: Result < ( ) > {
19- use anyhow:: Context ;
20-
2120 let Some ( render_interface) = render_interface else {
2221 return Ok ( ( ) ) ;
2322 } ;
@@ -30,69 +29,89 @@ pub fn capture_and_compare_image(
3029
3130 let expected_image = {
3231 let path = base_path. join ( format ! ( "{name}.expected.png" ) ) ?;
33- if path. is_file ( ) ? {
32+ if path. exists ( ) ? {
3433 image:: load_from_memory ( & read_bytes ( & path) ?)
3534 . context ( "Failed to open expected image" ) ?
3635 . into_rgba8 ( )
3736 } else if image_comparison. known_failure {
3837 // If we're expecting this to be wrong, don't save a likely wrong image
3938 return Err ( anyhow ! ( "Image '{name}': No image to compare to!" ) ) ;
4039 } else {
41- write_image ( & path, & actual_image, ImageFormat :: Png ) ?;
40+ write_image ( & path, & actual_image) ?;
4241 return Err ( anyhow ! (
4342 "Image '{name}': No image to compare to! Saved actual image as expected."
4443 ) ) ;
4544 }
4645 } ;
4746
48- let result = test (
49- & image_comparison,
50- name,
51- actual_image,
52- expected_image,
53- base_path,
54- render_interface. name ( ) ,
55- // If we're expecting failure, spamming files isn't productive.
56- !image_comparison. known_failure ,
57- ) ;
47+ let diff = test ( & image_comparison, name, & actual_image, expected_image) ?;
48+ let ( failure, failure_name) = match ( diff, image_comparison. known_failure ) {
49+ ( None , false ) => return Ok ( ( ) ) ,
50+ ( None , true ) => {
51+ return Err ( anyhow ! (
52+ "Image '{name}': Check was known to be failing, but now passes successfully. \
53+ Please update the test, and remove `known_failure = true` and `{name}.ruffle.png`!",
54+ ) ) ;
55+ }
56+ ( Some ( diff) , false ) => ( diff, Cow :: Borrowed ( name) ) ,
57+ ( Some ( _) , true ) => {
58+ let ruffle_name = format ! ( "{name}.ruffle" ) ;
59+ let path = base_path. join ( format ! ( "{ruffle_name}.png" ) ) ?;
60+ let image = if path. exists ( ) ? {
61+ image:: load_from_memory ( & read_bytes ( & path) ?)
62+ . context ( "Failed to open Ruffle-expected image" ) ?
63+ . into_rgba8 ( )
64+ } else {
65+ write_image ( & path, & actual_image) ?;
66+ return Err ( anyhow ! (
67+ "Image '{ruffle_name}': No image to compare to! Saved actual image as Ruffle-expected."
68+ ) ) ;
69+ } ;
70+
71+ if let Some ( diff) = test ( & image_comparison, & ruffle_name, & actual_image, image) ? {
72+ ( diff, Cow :: Owned ( ruffle_name) )
73+ } else {
74+ return Ok ( ( ) ) ;
75+ }
76+ }
77+ } ;
5878
59- match ( result, image_comparison. known_failure ) {
60- ( result, false ) => result,
61- ( Ok ( ( ) ) , true ) => Err ( anyhow ! (
62- "Image '{name}': Check was known to be failing, but now passes successfully. \
63- Please update the test and remove `known_failure = true`!",
64- ) ) ,
65- ( Err ( _) , true ) => Ok ( ( ) ) ,
79+ // A comparison failed: write difference images and return an error.
80+ let env_name = render_interface. name ( ) ;
81+ write_image (
82+ & base_path. join ( format ! ( "{name}.actual-{env_name}.png" ) ) ?,
83+ & actual_image,
84+ ) ?;
85+ write_image (
86+ & base_path. join ( format ! ( "{failure_name}.difference-color-{env_name}.png" ) ) ?,
87+ & failure. difference_color ( ) ?,
88+ ) ?;
89+ if let Some ( diff_alpha) = failure. difference_alpha ( ) ? {
90+ write_image (
91+ & base_path. join ( format ! ( "{failure_name}.difference-alpha-{env_name}.png" ) ) ?,
92+ & diff_alpha,
93+ ) ?;
6694 }
95+
96+ Err ( anyhow ! (
97+ "{failure_name} failed: \
98+ Number of outliers ({}) is bigger than allowed limit of {}. \
99+ Max difference is {}",
100+ failure. outliers,
101+ failure. max_outliers,
102+ failure. max_difference,
103+ ) )
67104}
68105
69- pub fn test (
106+ fn test (
70107 comparison : & ImageComparison ,
71108 name : & str ,
72- actual_image : image:: RgbaImage ,
109+ actual_image : & image:: RgbaImage ,
73110 expected_image : image:: RgbaImage ,
74- test_path : & VfsPath ,
75- environment_name : String ,
76- save_failures : bool ,
77- ) -> anyhow:: Result < ( ) > {
78- use anyhow:: Context ;
79-
80- let save_actual_image = || {
81- if save_failures {
82- write_image (
83- & test_path. join ( format ! ( "{name}.actual-{environment_name}.png" ) ) ?,
84- & actual_image,
85- ImageFormat :: Png ,
86- )
87- } else {
88- Ok ( ( ) )
89- }
90- } ;
91-
111+ ) -> anyhow:: Result < Option < ImageDiff > > {
92112 if actual_image. width ( ) != expected_image. width ( )
93113 || actual_image. height ( ) != expected_image. height ( )
94114 {
95- save_actual_image ( ) ?;
96115 return Err ( anyhow ! (
97116 "'{}' image is not the right size. Expected = {}x{}, actual = {}x{}." ,
98117 name,
@@ -106,7 +125,7 @@ pub fn test(
106125 let mut is_alpha_different = false ;
107126
108127 let difference_data: Vec < u8 > =
109- calculate_difference_data ( & actual_image, & expected_image, & mut is_alpha_different) ;
128+ calculate_difference_data ( actual_image, & expected_image, & mut is_alpha_different) ;
110129
111130 let checks = comparison
112131 . checks ( )
@@ -138,63 +157,61 @@ pub fn test(
138157 }
139158
140159 // The image failed a check :(
160+ return Ok ( Some ( ImageDiff {
161+ width : actual_image. width ( ) ,
162+ height : actual_image. height ( ) ,
163+ difference_data,
164+ outliers,
165+ max_outliers,
166+ max_difference,
167+ is_alpha_different,
168+ } ) ) ;
169+ }
141170
142- save_actual_image ( ) ?;
171+ if !any_check_executed {
172+ return Err ( anyhow ! ( "Image '{name}' failed: No checks executed." ) ) ;
173+ }
143174
175+ Ok ( None )
176+ }
177+
178+ struct ImageDiff {
179+ width : u32 ,
180+ height : u32 ,
181+ difference_data : Vec < u8 > ,
182+ outliers : usize ,
183+ max_outliers : usize ,
184+ max_difference : u8 ,
185+ is_alpha_different : bool ,
186+ }
187+
188+ impl ImageDiff {
189+ fn difference_color ( & self ) -> anyhow:: Result < image:: RgbImage > {
144190 let mut difference_color =
145- Vec :: with_capacity ( actual_image . width ( ) as usize * actual_image . height ( ) as usize * 3 ) ;
146- for p in difference_data. chunks_exact ( 4 ) {
191+ Vec :: with_capacity ( self . width as usize * self . height as usize * 3 ) ;
192+ for p in self . difference_data . chunks_exact ( 4 ) {
147193 difference_color. extend_from_slice ( & p[ ..3 ] ) ;
148194 }
149195
150- if save_failures {
151- let difference_image = image:: RgbImage :: from_raw (
152- actual_image. width ( ) ,
153- actual_image. height ( ) ,
154- difference_color,
155- )
156- . context ( "Couldn't create color difference image" ) ?;
157- write_image (
158- & test_path. join ( format ! ( "{name}.difference-color-{environment_name}.png" ) ) ?,
159- & difference_image,
160- ImageFormat :: Png ,
161- ) ?;
162- }
196+ image:: RgbImage :: from_raw ( self . width , self . height , difference_color)
197+ . context ( "Couldn't create color difference image" )
198+ }
163199
164- if is_alpha_different {
200+ fn difference_alpha ( & self ) -> anyhow:: Result < Option < image:: GrayImage > > {
201+ if self . is_alpha_different {
165202 let mut difference_alpha =
166- Vec :: with_capacity ( actual_image . width ( ) as usize * actual_image . height ( ) as usize ) ;
167- for p in difference_data. chunks_exact ( 4 ) {
203+ Vec :: with_capacity ( self . width as usize * self . height as usize ) ;
204+ for p in self . difference_data . chunks_exact ( 4 ) {
168205 difference_alpha. push ( p[ 3 ] )
169206 }
170207
171- if save_failures {
172- let difference_image = image:: GrayImage :: from_raw (
173- actual_image. width ( ) ,
174- actual_image. height ( ) ,
175- difference_alpha,
176- )
177- . context ( "Couldn't create alpha difference image" ) ?;
178- write_image (
179- & test_path. join ( format ! ( "{name}.difference-alpha-{environment_name}.png" ) ) ?,
180- & difference_image,
181- ImageFormat :: Png ,
182- ) ?;
183- }
208+ image:: GrayImage :: from_raw ( self . width , self . height , difference_alpha)
209+ . context ( "Couldn't create alpha difference image" )
210+ . map ( Some )
211+ } else {
212+ Ok ( None )
184213 }
185-
186- return Err ( anyhow ! (
187- "{check_name} failed: \
188- Number of outliers ({outliers}) is bigger than allowed limit of {max_outliers}. \
189- Max difference is {max_difference}",
190- ) ) ;
191214 }
192-
193- if !any_check_executed {
194- return Err ( anyhow ! ( "Image '{name}' failed: No checks executed." , ) ) ;
195- }
196-
197- Ok ( ( ) )
198215}
199216
200217fn calculate_difference_data (
@@ -248,15 +265,14 @@ fn calc_difference(lhs: u8, rhs: u8) -> u8 {
248265fn write_image < P , Container > (
249266 path : & VfsPath ,
250267 image : & ImageBuffer < P , Container > ,
251- format : ImageFormat ,
252268) -> anyhow:: Result < ( ) >
253269where
254270 P : Pixel + PixelWithColorType ,
255271 [ P :: Subpixel ] : EncodableLayout ,
256272 Container : Deref < Target = [ P :: Subpixel ] > ,
257273{
258274 let mut buffer = vec ! [ ] ;
259- image. write_to ( & mut Cursor :: new ( & mut buffer) , format ) ?;
275+ image. write_to ( & mut Cursor :: new ( & mut buffer) , ImageFormat :: Png ) ?;
260276 write_bytes ( path, & buffer) ?;
261277 Ok ( ( ) )
262278}
0 commit comments