@@ -26,7 +26,9 @@ const BASELINE_DIR = path.join(".github", "test-artifacts", "pdf-baselines");
2626const OUTPUT_DIR = path . join ( "output" , "playwright" , "pdf-visual" ) ;
2727const VIEWPORT = { width : 1200 , height : 760 } ;
2828const PIXELMATCH_THRESHOLD = 0.1 ;
29- const MAX_DIFF_RATIO = 0.003 ;
29+ const DHASH_WIDTH = 17 ;
30+ const DHASH_HEIGHT = 16 ;
31+ const MAX_DHASH_DISTANCE = 20 ;
3032
3133function parseArgs ( argv ) {
3234 return {
@@ -49,6 +51,38 @@ function pngPaths(name) {
4951 } ;
5052}
5153
54+ function grayscaleAt ( image , x , y ) {
55+ const index = ( y * image . width + x ) * 4 ;
56+ const r = image . data [ index ] ;
57+ const g = image . data [ index + 1 ] ;
58+ const b = image . data [ index + 2 ] ;
59+ return 0.299 * r + 0.587 * g + 0.114 * b ;
60+ }
61+
62+ function differenceHash ( image , width = DHASH_WIDTH , height = DHASH_HEIGHT ) {
63+ const bits = [ ] ;
64+ for ( let y = 0 ; y < height ; y += 1 ) {
65+ for ( let x = 0 ; x < width - 1 ; x += 1 ) {
66+ const leftX = Math . floor ( ( ( x + 0.5 ) * image . width ) / width ) ;
67+ const rightX = Math . floor ( ( ( x + 1.5 ) * image . width ) / width ) ;
68+ const sampleY = Math . floor ( ( ( y + 0.5 ) * image . height ) / height ) ;
69+ bits . push ( grayscaleAt ( image , leftX , sampleY ) > grayscaleAt ( image , rightX , sampleY ) ? 1 : 0 ) ;
70+ }
71+ }
72+ return bits ;
73+ }
74+
75+ function hammingDistance ( left , right ) {
76+ assert ( left . length === right . length , "Cannot compare hashes of different lengths" ) ;
77+ let distance = 0 ;
78+ for ( let index = 0 ; index < left . length ; index += 1 ) {
79+ if ( left [ index ] !== right [ index ] ) {
80+ distance += 1 ;
81+ }
82+ }
83+ return distance ;
84+ }
85+
5286function contentTypeFor ( filePath ) {
5387 if ( filePath . endsWith ( ".pdf" ) ) {
5488 return "application/pdf" ;
@@ -141,20 +175,16 @@ async function compareCase(browser, baseUrl, caseSpec) {
141175 actual . height ,
142176 { threshold : PIXELMATCH_THRESHOLD }
143177 ) ;
144- const totalPixels = actual . width * actual . height ;
145- const diffRatio = diffPixels / totalPixels ;
146-
147178 await writeFile ( paths . diff , PNG . sync . write ( diff ) ) ;
179+ const dHashDistance = hammingDistance ( differenceHash ( baseline ) , differenceHash ( actual ) ) ;
148180
149- if ( diffRatio > MAX_DIFF_RATIO ) {
181+ if ( dHashDistance > MAX_DHASH_DISTANCE ) {
150182 throw new Error (
151- `${ caseSpec . name } visual diff too large: ${ diffPixels } pixels ( ${ ( diffRatio * 100 ) . toFixed ( 3 ) } %) `
183+ `${ caseSpec . name } perceptual diff too large: dHash distance ${ dHashDistance } , raw diff ${ diffPixels } pixels `
152184 ) ;
153185 }
154186
155- console . log (
156- `OK [${ caseSpec . name } ] visual diff ${ diffPixels } pixels (${ ( diffRatio * 100 ) . toFixed ( 3 ) } %)`
157- ) ;
187+ console . log ( `OK [${ caseSpec . name } ] dHash distance ${ dHashDistance } , raw diff ${ diffPixels } pixels` ) ;
158188}
159189
160190async function main ( ) {
0 commit comments