@@ -46,7 +46,7 @@ export default function pixelmatch(img1, img2, output, width, height, options =
4646 }
4747 if ( identical ) { // fast path if identical
4848 if ( output && ! diffMask ) {
49- for ( let i = 0 ; i < len ; i ++ ) drawGrayPixel ( img1 , 4 * i , alpha , output ) ;
49+ for ( let i = 0 , pos = 0 ; i < len ; i ++ , pos += 4 ) drawGrayPixel ( img1 , pos , alpha , output ) ;
5050 }
5151 return 0 ;
5252 }
@@ -60,40 +60,36 @@ export default function pixelmatch(img1, img2, output, width, height, options =
6060 let diff = 0 ;
6161
6262 // compare each pixel of one image against the other one
63- for ( let y = 0 ; y < height ; y ++ ) {
64- for ( let x = 0 ; x < width ; x ++ ) {
65-
66- const i = y * width + x ;
67- const pos = i * 4 ;
68-
69- // squared YUV distance between colors at this pixel position, negative if the img2 pixel is darker
70- const delta = a32 [ i ] === b32 [ i ] ? 0 : colorDelta ( img1 , img2 , pos , pos , false ) ;
71-
72- // the color difference is above the threshold
73- if ( Math . abs ( delta ) > maxDelta ) {
74- // check it's a real rendering difference or just anti-aliasing
75- const isExcludedAA = ! includeAA && ( antialiased ( img1 , x , y , width , height , a32 , b32 ) || antialiased ( img2 , x , y , width , height , b32 , a32 ) ) ;
76- if ( isExcludedAA ) {
77- // one of the pixels is anti-aliasing; draw as yellow and do not count as difference
78- // note that we do not include such pixels in a mask
79- if ( output && ! diffMask ) drawPixel ( output , pos , aaR , aaG , aaB ) ;
80-
81- } else {
82- // found substantial difference not caused by anti-aliasing; draw it as such
83- if ( output ) {
84- if ( delta < 0 ) {
85- drawPixel ( output , pos , altR , altG , altB ) ;
86- } else {
87- drawPixel ( output , pos , diffR , diffG , diffB ) ;
88- }
63+ for ( let i = 0 , pos = 0 ; i < len ; i ++ , pos += 4 ) {
64+ // squared YUV distance between colors at this pixel position, negative if the img2 pixel is darker
65+ const delta = a32 [ i ] === b32 [ i ] ? 0 : colorDelta ( img1 , img2 , pos , pos ) ;
66+
67+ // the color difference is above the threshold
68+ if ( Math . abs ( delta ) > maxDelta ) {
69+ const x = i % width ;
70+ const y = ( i / width ) | 0 ;
71+ // check it's a real rendering difference or just anti-aliasing
72+ const isExcludedAA = ! includeAA && ( antialiased ( img1 , x , y , width , height , a32 , b32 ) || antialiased ( img2 , x , y , width , height , b32 , a32 ) ) ;
73+ if ( isExcludedAA ) {
74+ // one of the pixels is anti-aliasing; draw as yellow and do not count as difference
75+ // note that we do not include such pixels in a mask
76+ if ( output && ! diffMask ) drawPixel ( output , pos , aaR , aaG , aaB ) ;
77+
78+ } else {
79+ // found substantial difference not caused by anti-aliasing; draw it as such
80+ if ( output ) {
81+ if ( delta < 0 ) {
82+ drawPixel ( output , pos , altR , altG , altB ) ;
83+ } else {
84+ drawPixel ( output , pos , diffR , diffG , diffB ) ;
8985 }
90- diff ++ ;
9186 }
92-
93- } else if ( output && ! diffMask ) {
94- // pixels are similar; draw background as grayscale image blended with white
95- drawGrayPixel ( img1 , pos , alpha , output ) ;
87+ diff ++ ;
9688 }
89+
90+ } else if ( output && ! diffMask ) {
91+ // pixels are similar; draw background as grayscale image blended with white
92+ drawGrayPixel ( img1 , pos , alpha , output ) ;
9793 }
9894 }
9995
@@ -123,7 +119,12 @@ function antialiased(img, x1, y1, width, height, a32, b32) {
123119 const y0 = Math . max ( y1 - 1 , 0 ) ;
124120 const x2 = Math . min ( x1 + 1 , width - 1 ) ;
125121 const y2 = Math . min ( y1 + 1 , height - 1 ) ;
126- const pos = y1 * width + x1 ;
122+ const pos4 = ( y1 * width + x1 ) * 4 ;
123+ // cache the center pixel's RGBA once instead of re-reading it on every neighbor comparison
124+ const cr = img [ pos4 ] ;
125+ const cg = img [ pos4 + 1 ] ;
126+ const cb = img [ pos4 + 2 ] ;
127+ const ca = img [ pos4 + 3 ] ;
127128 let zeroes = x1 === x0 || x1 === x2 || y1 === y0 || y1 === y2 ? 1 : 0 ;
128129 let min = 0 ;
129130 let max = 0 ;
@@ -138,7 +139,7 @@ function antialiased(img, x1, y1, width, height, a32, b32) {
138139 if ( x === x1 && y === y1 ) continue ;
139140
140141 // brightness delta between the center pixel and adjacent one
141- const delta = colorDelta ( img , img , pos * 4 , ( y * width + x ) * 4 , true ) ;
142+ const delta = brightnessDelta ( img , pos4 , ( y * width + x ) * 4 , cr , cg , cb , ca ) ;
142143
143144 // count the number of equal, darker and brighter adjacent pixels
144145 if ( delta === 0 ) {
@@ -199,14 +200,14 @@ function hasManySiblings(img, x1, y1, width, height) {
199200
200201/**
201202 * Calculate color difference according to the paper "Measuring perceived color difference
202- * using YIQ NTSC transmission color space in mobile applications" by Y. Kotsarenko and F. Ramos
203+ * using YIQ NTSC transmission color space in mobile applications" by Y. Kotsarenko and F. Ramos.
204+ * Caller guarantees the two pixels differ, so the early-zero check is omitted.
203205 * @param {Uint8Array | Uint8ClampedArray } img1
204206 * @param {Uint8Array | Uint8ClampedArray } img2
205207 * @param {number } k
206208 * @param {number } m
207- * @param {boolean } yOnly
208209 */
209- function colorDelta ( img1 , img2 , k , m , yOnly ) {
210+ function colorDelta ( img1 , img2 , k , m ) {
210211 const r1 = img1 [ k ] ;
211212 const g1 = img1 [ k + 1 ] ;
212213 const b1 = img1 [ k + 2 ] ;
@@ -221,8 +222,6 @@ function colorDelta(img1, img2, k, m, yOnly) {
221222 let db = b1 - b2 ;
222223 const da = a1 - a2 ;
223224
224- if ( ! dr && ! dg && ! db && ! da ) return 0 ;
225-
226225 if ( a1 < 255 || a2 < 255 ) { // blend pixels with background
227226 const rb = 48 + 159 * ( k % 2 ) ;
228227 const gb = 48 + 159 * ( ( k / 1.618033988749895 | 0 ) % 2 ) ;
@@ -233,9 +232,6 @@ function colorDelta(img1, img2, k, m, yOnly) {
233232 }
234233
235234 const y = dr * 0.29889531 + dg * 0.58662247 + db * 0.11448223 ;
236-
237- if ( yOnly ) return y ; // brightness difference only
238-
239235 const i = dr * 0.59597799 - dg * 0.27417610 - db * 0.32180189 ;
240236 const q = dr * 0.21147017 - dg * 0.52261711 + db * 0.31114694 ;
241237
@@ -245,6 +241,42 @@ function colorDelta(img1, img2, k, m, yOnly) {
245241 return y > 0 ? - delta : delta ;
246242}
247243
244+ /**
245+ * Specialized brightness-only color delta for the anti-aliasing detector,
246+ * with the center pixel's RGBA hoisted out of the neighbor loop.
247+ * @param {Uint8Array | Uint8ClampedArray } img
248+ * @param {number } k center pixel offset
249+ * @param {number } m neighbor pixel offset
250+ * @param {number } r1
251+ * @param {number } g1
252+ * @param {number } b1
253+ * @param {number } a1
254+ */
255+ function brightnessDelta ( img , k , m , r1 , g1 , b1 , a1 ) {
256+ const r2 = img [ m ] ;
257+ const g2 = img [ m + 1 ] ;
258+ const b2 = img [ m + 2 ] ;
259+ const a2 = img [ m + 3 ] ;
260+
261+ let dr = r1 - r2 ;
262+ let dg = g1 - g2 ;
263+ let db = b1 - b2 ;
264+ const da = a1 - a2 ;
265+
266+ if ( ! dr && ! dg && ! db && ! da ) return 0 ;
267+
268+ if ( a1 < 255 || a2 < 255 ) {
269+ const rb = 48 + 159 * ( k % 2 ) ;
270+ const gb = 48 + 159 * ( ( k / 1.618033988749895 | 0 ) % 2 ) ;
271+ const bb = 48 + 159 * ( ( k / 2.618033988749895 | 0 ) % 2 ) ;
272+ dr = ( r1 * a1 - r2 * a2 - rb * da ) / 255 ;
273+ dg = ( g1 * a1 - g2 * a2 - gb * da ) / 255 ;
274+ db = ( b1 * a1 - b2 * a2 - bb * da ) / 255 ;
275+ }
276+
277+ return dr * 0.29889531 + dg * 0.58662247 + db * 0.11448223 ;
278+ }
279+
248280/**
249281 * @param {Uint8Array | Uint8ClampedArray } output
250282 * @param {number } pos
@@ -253,7 +285,7 @@ function colorDelta(img1, img2, k, m, yOnly) {
253285 * @param {number } b
254286 */
255287function drawPixel ( output , pos , r , g , b ) {
256- output [ pos + 0 ] = r ;
288+ output [ pos ] = r ;
257289 output [ pos + 1 ] = g ;
258290 output [ pos + 2 ] = b ;
259291 output [ pos + 3 ] = 255 ;
0 commit comments