@@ -21,6 +21,33 @@ use vello_cpu::{Level, RenderMode};
2121#[ cfg( not( target_arch = "wasm32" ) ) ]
2222use std:: path:: PathBuf ;
2323
24+ /// Aggregate diff report with statistics and individual pixel differences.
25+ #[ cfg( not( target_arch = "wasm32" ) ) ]
26+ #[ derive( Debug , serde:: Serialize ) ]
27+ pub ( crate ) struct DiffReport {
28+ /// Total number of pixels that differ.
29+ pub pixel_count : usize ,
30+ /// Maximum absolute difference per channel [R, G, B, A].
31+ pub max_difference : [ i16 ; 4 ] ,
32+ /// Individual pixel differences.
33+ pub pixels : Vec < PixelDiff > ,
34+ }
35+
36+ /// Represents a single pixel difference between reference and actual images.
37+ #[ derive( Debug , serde:: Serialize ) ]
38+ pub ( crate ) struct PixelDiff {
39+ /// The x coordinate of the differing pixel.
40+ pub x : u32 ,
41+ /// The y coordinate of the differing pixel.
42+ pub y : u32 ,
43+ /// The RGBA values from the reference image.
44+ pub reference : [ u8 ; 4 ] ,
45+ /// The RGBA values from the actual image.
46+ pub actual : [ u8 ; 4 ] ,
47+ /// Per-channel difference (actual - reference) as signed values.
48+ pub difference : [ i16 ; 4 ] ,
49+ }
50+
2451#[ cfg( not( target_arch = "wasm32" ) ) ]
2552static REFS_PATH : std:: sync:: LazyLock < PathBuf > = std:: sync:: LazyLock :: new ( || {
2653 PathBuf :: from ( env ! ( "CARGO_MANIFEST_DIR" ) ) . join ( "../vello_sparse_tests/snapshots" )
@@ -321,9 +348,9 @@ pub(crate) fn check_ref(
321348 . into_rgba8 ( ) ;
322349 let actual = load_from_memory ( & encoded_image) . unwrap ( ) . into_rgba8 ( ) ;
323350
324- let diff_image = get_diff ( & ref_image, & actual, threshold, diff_pixels) ;
351+ let diff_result = get_diff ( & ref_image, & actual, threshold, diff_pixels) ;
325352
326- if let Some ( diff_image) = diff_image {
353+ if let Some ( ( diff_image, diff_data ) ) = diff_result {
327354 if should_replace ( ) && is_reference {
328355 write_ref_image ( ) ;
329356 panic ! ( "test was replaced" ) ;
@@ -338,6 +365,22 @@ pub(crate) fn check_ref(
338365 . save_with_format ( & diff_path, image:: ImageFormat :: Png )
339366 . unwrap ( ) ;
340367
368+ // Save diff data as JSON
369+ let json_path = DIFFS_PATH . join ( format ! ( "{specific_name}.json" ) ) ;
370+ let max_difference: [ i16 ; 4 ] = diff_data. iter ( ) . fold ( [ 0 ; 4 ] , |mut max, p| {
371+ for ( m, d) in max. iter_mut ( ) . zip ( & p. difference ) {
372+ * m = ( * m) . max ( d. abs ( ) ) ;
373+ }
374+ max
375+ } ) ;
376+ let report = DiffReport {
377+ pixel_count : diff_data. len ( ) ,
378+ max_difference,
379+ pixels : diff_data,
380+ } ;
381+ let json_data = serde_json:: to_string_pretty ( & report) . unwrap ( ) ;
382+ std:: fs:: write ( & json_path, json_data) . unwrap ( ) ;
383+
341384 panic ! ( "test didnt match reference image" ) ;
342385 }
343386}
@@ -365,7 +408,7 @@ pub(crate) fn check_ref(
365408 let ref_image = load_from_memory ( ref_data) . unwrap ( ) . into_rgba8 ( ) ;
366409
367410 let diff_image = get_diff ( & ref_image, & actual, threshold, diff_pixels) ;
368- if let Some ( ref img) = diff_image {
411+ if let Some ( ( ref img, _ ) ) = diff_image {
369412 append_diff_image_to_browser_document ( specific_name, img) ;
370413 panic ! ( "test didn't match reference image. Scroll to bottom of browser to view diff." ) ;
371414 }
@@ -445,11 +488,12 @@ fn get_diff(
445488 actual_image : & RgbaImage ,
446489 threshold : u8 ,
447490 diff_pixels : u16 ,
448- ) -> Option < RgbaImage > {
491+ ) -> Option < ( RgbaImage , Vec < PixelDiff > ) > {
449492 let width = max ( expected_image. width ( ) , actual_image. width ( ) ) ;
450493 let height = max ( expected_image. height ( ) , actual_image. height ( ) ) ;
451494
452495 let mut diff_image = RgbaImage :: new ( width * 3 , height) ;
496+ let mut diff_data = Vec :: new ( ) ;
453497
454498 let mut pixel_diff = 0 ;
455499
@@ -465,6 +509,18 @@ fn get_diff(
465509 if is_pix_diff ( expected, actual, threshold) {
466510 pixel_diff += 1 ;
467511 diff_image. put_pixel ( x + width, y, Rgba ( [ 255 , 0 , 0 , 255 ] ) ) ;
512+ diff_data. push ( PixelDiff {
513+ x,
514+ y,
515+ reference : expected. 0 ,
516+ actual : actual. 0 ,
517+ difference : [
518+ i16:: from ( actual. 0 [ 0 ] ) - i16:: from ( expected. 0 [ 0 ] ) ,
519+ i16:: from ( actual. 0 [ 1 ] ) - i16:: from ( expected. 0 [ 1 ] ) ,
520+ i16:: from ( actual. 0 [ 2 ] ) - i16:: from ( expected. 0 [ 2 ] ) ,
521+ i16:: from ( actual. 0 [ 3 ] ) - i16:: from ( expected. 0 [ 3 ] ) ,
522+ ] ,
523+ } ) ;
468524 } else {
469525 diff_image. put_pixel ( x + width, y, Rgba ( [ 0 , 0 , 0 , 255 ] ) ) ;
470526 }
@@ -473,23 +529,54 @@ fn get_diff(
473529 pixel_diff += 1 ;
474530 diff_image. put_pixel ( x + 2 * width, y, * actual) ;
475531 diff_image. put_pixel ( x + width, y, Rgba ( [ 255 , 0 , 0 , 255 ] ) ) ;
532+ diff_data. push ( PixelDiff {
533+ x,
534+ y,
535+ reference : [ 0 , 0 , 0 , 0 ] ,
536+ actual : actual. 0 ,
537+ difference : [
538+ i16:: from ( actual. 0 [ 0 ] ) ,
539+ i16:: from ( actual. 0 [ 1 ] ) ,
540+ i16:: from ( actual. 0 [ 2 ] ) ,
541+ i16:: from ( actual. 0 [ 3 ] ) ,
542+ ] ,
543+ } ) ;
476544 }
477545 ( None , Some ( expected) ) => {
478546 pixel_diff += 1 ;
479547 diff_image. put_pixel ( x, y, * expected) ;
480548 diff_image. put_pixel ( x + width, y, Rgba ( [ 255 , 0 , 0 , 255 ] ) ) ;
549+ diff_data. push ( PixelDiff {
550+ x,
551+ y,
552+ reference : expected. 0 ,
553+ actual : [ 0 , 0 , 0 , 0 ] ,
554+ difference : [
555+ -i16:: from ( expected. 0 [ 0 ] ) ,
556+ -i16:: from ( expected. 0 [ 1 ] ) ,
557+ -i16:: from ( expected. 0 [ 2 ] ) ,
558+ -i16:: from ( expected. 0 [ 3 ] ) ,
559+ ] ,
560+ } ) ;
481561 }
482562 _ => {
483563 pixel_diff += 1 ;
484564 diff_image. put_pixel ( x, y, Rgba ( [ 255 , 0 , 0 , 255 ] ) ) ;
485565 diff_image. put_pixel ( x + width, y, Rgba ( [ 255 , 0 , 0 , 255 ] ) ) ;
566+ diff_data. push ( PixelDiff {
567+ x,
568+ y,
569+ reference : [ 0 , 0 , 0 , 0 ] ,
570+ actual : [ 0 , 0 , 0 , 0 ] ,
571+ difference : [ 0 , 0 , 0 , 0 ] ,
572+ } ) ;
486573 }
487574 }
488575 }
489576 }
490577
491578 if pixel_diff > diff_pixels {
492- Some ( diff_image)
579+ Some ( ( diff_image, diff_data ) )
493580 } else {
494581 None
495582 }
0 commit comments