@@ -125,7 +125,9 @@ function filter(data: Uint8Array, rowBytes: number, height: number, bpp: number)
125125 for ( let i = bpp ; i < rowBytes ; i ++ ) {
126126 const a = data [ row + i - bpp ] , b = data [ row + i - rowBytes ] , c = data [ row + i - rowBytes - bpp ] ;
127127 const pa = Math . abs ( b - c ) , pb = Math . abs ( a - c ) , pc = Math . abs ( a + b - 2 * c ) ;
128- out [ o + i ] = data [ row + i ] - ( pa <= pb && pa <= pc ? a : pb <= pc ? b : c ) ;
128+ // the predictor without branches, see unfilter
129+ const notA = ( pb - pa | pc - pa ) >> 31 , takeC = ( pc - pb ) >> 31 ;
130+ out [ o + i ] = data [ row + i ] - ( a ^ ( a ^ b ^ ( b ^ c ) & takeC ) & notA ) ;
129131 }
130132 }
131133
@@ -191,7 +193,7 @@ function pixelFormat(images: Uint8Array[]): PixelFormat {
191193
192194 // collect up to 257 distinct colors, runs of equal pixels are skipped cheaply
193195 let colors : Map < number , number > | null = new Map ( ) ;
194- for ( const rgba of images ) {
196+ scan: for ( const rgba of images ) {
195197 const pixels = new Uint32Array ( rgba . buffer , rgba . byteOffset , rgba . length >> 2 ) ;
196198 let previous = - 1 ;
197199 for ( let i = 0 ; i < pixels . length ; i ++ ) {
@@ -206,6 +208,8 @@ function pixelFormat(images: Uint8Array[]): PixelFormat {
206208 if ( colors . size === 256 ) colors = null ;
207209 else colors . set ( value , colors . size ) ;
208210 }
211+ // nothing left to find out: RGBA it is
212+ if ( ! colors && ! opaque && ! grey ) break scan;
209213 }
210214 }
211215
@@ -253,16 +257,18 @@ function pixelFormat(images: Uint8Array[]): PixelFormat {
253257 if ( channels === 4 ) return rgba ;
254258 const count = width * height ;
255259 const data = new Uint8Array ( count * channels ) ;
256- for ( let i = 0 , o = 0 ; i < count ; i ++ ) {
257- const s = i * 4 ;
258- if ( grey ) data [ o ++ ] = rgba [ s ] ;
259- else {
260- data [ o ++ ] = rgba [ s ] ;
261- data [ o ++ ] = rgba [ s + 1 ] ;
262- data [ o ++ ] = rgba [ s + 2 ] ;
260+ if ( channels === 3 ) {
261+ for ( let s = 0 , o = 0 ; o < data . length ; s += 4 , o += 3 ) {
262+ data [ o ] = rgba [ s ] ;
263+ data [ o + 1 ] = rgba [ s + 1 ] ;
264+ data [ o + 2 ] = rgba [ s + 2 ] ;
263265 }
264- if ( ! opaque ) data [ o ++ ] = rgba [ s + 3 ] ;
265- }
266+ } else if ( channels === 2 ) {
267+ for ( let s = 0 , o = 0 ; o < data . length ; s += 4 , o += 2 ) {
268+ data [ o ] = rgba [ s ] ;
269+ data [ o + 1 ] = rgba [ s + 3 ] ;
270+ }
271+ } else for ( let s = 0 , o = 0 ; o < data . length ; s += 4 , o ++ ) data [ o ] = rgba [ s ] ;
266272 return data ;
267273 }
268274 } ;
@@ -747,7 +753,9 @@ function unfilter(data: Uint8Array, offset: number, height: number, rowBytes: nu
747753 for ( let i = row + bpp ; i < row + rowBytes ; i ++ ) {
748754 const a = out [ i - bpp ] , b = out [ i - rowBytes ] , c = out [ i - rowBytes - bpp ] ;
749755 const pa = Math . abs ( b - c ) , pb = Math . abs ( a - c ) , pc = Math . abs ( a + b - 2 * c ) ;
750- out [ i ] = data [ src + i ] + ( pa <= pb && pa <= pc ? a : pb <= pc ? b : c ) ;
756+ // without branches (photos make them unpredictable): all ones when a loses to b or c, when c beats b
757+ const notA = ( pb - pa | pc - pa ) >> 31 , takeC = ( pc - pb ) >> 31 ;
758+ out [ i ] = data [ src + i ] + ( a ^ ( a ^ b ^ ( b ^ c ) & takeC ) & notA ) ;
751759 }
752760 }
753761 } else throw new Error ( `PNG: invalid filter type ${ filter } ` ) ;
@@ -824,6 +832,22 @@ function createConverter(colorType: number, depth: number, palette: Uint8Array |
824832 ? [ transparency [ 0 ] << 8 | transparency [ 1 ] , transparency [ 2 ] << 8 | transparency [ 3 ] , transparency [ 4 ] << 8 | transparency [ 5 ] ] . map ( v => v & maxValue )
825833 : null ;
826834
835+ // 8-bit samples are bytes that need no scaling, read directly (the most common kind of opaque PNG)
836+ if ( depth === 8 ) {
837+ const [ kr , kg , kb ] = key || [ - 1 , - 1 , - 1 ] ;
838+ return ( pixels , rowBytes , w , h , out , place ) => {
839+ for ( let y = 0 ; y < h ; y ++ ) {
840+ for ( let x = 0 , s = y * rowBytes , o = place . row ( y ) , step = place . step ; x < w ; x ++ , s += 3 , o += step ) {
841+ const r = pixels [ s ] , g = pixels [ s + 1 ] , b = pixels [ s + 2 ] ;
842+ out [ o ] = r ;
843+ out [ o + 1 ] = g ;
844+ out [ o + 2 ] = b ;
845+ out [ o + 3 ] = r === kr && g === kg && b === kb ? 0 : 0xff ;
846+ }
847+ }
848+ } ;
849+ }
850+
827851 return ( pixels , rowBytes , w , h , out , place ) => {
828852 for ( let y = 0 ; y < h ; y ++ ) {
829853 const row = pixels . subarray ( y * rowBytes , ( y + 1 ) * rowBytes ) ;
@@ -861,9 +885,14 @@ function createConverter(colorType: number, depth: number, palette: Uint8Array |
861885 // non-interlaced 8-bit RGBA maps 1:1 onto the output
862886 if ( place . step === 4 && w * 4 === rowBytes && place . row ( 0 ) === 0 && place . row ( 1 ) === rowBytes ) return out . set ( pixels ) ;
863887
864- for ( let y = 0 ; y < h ; y ++ )
865- for ( let x = 0 , o = place . row ( y ) ; x < w ; x ++ , o += place . step )
866- out . set ( pixels . subarray ( y * rowBytes + x * 4 , y * rowBytes + x * 4 + 4 ) , o ) ;
888+ for ( let y = 0 ; y < h ; y ++ ) {
889+ for ( let x = 0 , s = y * rowBytes , o = place . row ( y ) , step = place . step ; x < w ; x ++ , s += 4 , o += step ) {
890+ out [ o ] = pixels [ s ] ;
891+ out [ o + 1 ] = pixels [ s + 1 ] ;
892+ out [ o + 2 ] = pixels [ s + 2 ] ;
893+ out [ o + 3 ] = pixels [ s + 3 ] ;
894+ }
895+ }
867896 } ;
868897 }
869898
0 commit comments