@@ -370,24 +370,28 @@ function select(l: number, t: number, tl: number): number {
370370// adds two ARGB pixels per channel, modulo 256
371371const addPixels = ( a : number , b : number ) : number => ( ( a & 0xff00ff00 ) + ( b & 0xff00ff00 ) & 0xff00ff00 | ( a & 0x00ff00ff ) + ( b & 0x00ff00ff ) & 0x00ff00ff ) >>> 0 ;
372372
373- function predict ( mode : number , p : Uint32Array , i : number , width : number ) : number {
374- const l = p [ i - 1 ] , t = p [ i - width ] , tl = p [ i - width - 1 ] , tr = p [ i - width + 1 ] ;
373+ // predictions of `n` pixels from index i on (not in the first row or column) into out, from the pixels in p; with
374+ // out === p, every pixel is the residual plus its prediction from the pixels already restored (decoding)
375+ function predictRun ( mode : number , p : Uint32Array , i : number , n : number , width : number , out : Uint32Array , o : number ) : void {
376+ const restore = out === p ;
377+ const end = i + n ;
378+ // the switch runs once per run instead of once per pixel
375379 switch ( mode ) {
376- case 0 : return 0xff000000 ;
377- case 1 : return l ;
378- case 2 : return t ;
379- case 3 : return tr ;
380- case 4 : return tl ;
381- case 5 : return average2 ( average2 ( l , tr ) , t ) ;
382- case 6 : return average2 ( l , tl ) ;
383- case 7 : return average2 ( l , t ) ;
384- case 8 : return average2 ( tl , t ) ;
385- case 9 : return average2 ( t , tr ) ;
386- case 10 : return average2 ( average2 ( l , tl ) , average2 ( t , tr ) ) ;
387- case 11 : return select ( l , t , tl ) ;
388- case 12 : return clampAddSubtractFull ( l , t , tl ) ;
389- case 13 : return clampAddSubtractHalf ( average2 ( l , t ) , tl ) ;
390- default : return 0xff000000 ; // modes 14 and 15 aren't defined, libwebp predicts black like mode 0
380+ case 1 : for ( ; i < end ; i ++ , o ++ ) out [ o ] = restore ? addPixels ( p [ i ] , p [ i - 1 ] ) : p [ i - 1 ] ; return ;
381+ case 2 : for ( ; i < end ; i ++ , o ++ ) out [ o ] = restore ? addPixels ( p [ i ] , p [ i - width ] ) : p [ i - width ] ; return ;
382+ case 3 : for ( ; i < end ; i ++ , o ++ ) out [ o ] = restore ? addPixels ( p [ i ] , p [ i - width + 1 ] ) : p [ i - width + 1 ] ; return ;
383+ case 4 : for ( ; i < end ; i ++ , o ++ ) out [ o ] = restore ? addPixels ( p [ i ] , p [ i - width - 1 ] ) : p [ i - width - 1 ] ; return ;
384+ case 5 : for ( ; i < end ; i ++ , o ++ ) { const v = average2 ( average2 ( p [ i - 1 ] , p [ i - width + 1 ] ) , p [ i - width ] ) ; out [ o ] = restore ? addPixels ( p [ i ] , v ) : v ; } return ;
385+ case 6 : for ( ; i < end ; i ++ , o ++ ) { const v = average2 ( p [ i - 1 ] , p [ i - width - 1 ] ) ; out [ o ] = restore ? addPixels ( p [ i ] , v ) : v ; } return ;
386+ case 7 : for ( ; i < end ; i ++ , o ++ ) { const v = average2 ( p [ i - 1 ] , p [ i - width ] ) ; out [ o ] = restore ? addPixels ( p [ i ] , v ) : v ; } return ;
387+ case 8 : for ( ; i < end ; i ++ , o ++ ) { const v = average2 ( p [ i - width - 1 ] , p [ i - width ] ) ; out [ o ] = restore ? addPixels ( p [ i ] , v ) : v ; } return ;
388+ case 9 : for ( ; i < end ; i ++ , o ++ ) { const v = average2 ( p [ i - width ] , p [ i - width + 1 ] ) ; out [ o ] = restore ? addPixels ( p [ i ] , v ) : v ; } return ;
389+ case 10 : for ( ; i < end ; i ++ , o ++ ) { const v = average2 ( average2 ( p [ i - 1 ] , p [ i - width - 1 ] ) , average2 ( p [ i - width ] , p [ i - width + 1 ] ) ) ; out [ o ] = restore ? addPixels ( p [ i ] , v ) : v ; } return ;
390+ case 11 : for ( ; i < end ; i ++ , o ++ ) { const v = select ( p [ i - 1 ] , p [ i - width ] , p [ i - width - 1 ] ) ; out [ o ] = restore ? addPixels ( p [ i ] , v ) : v ; } return ;
391+ case 12 : for ( ; i < end ; i ++ , o ++ ) { const v = clampAddSubtractFull ( p [ i - 1 ] , p [ i - width ] , p [ i - width - 1 ] ) ; out [ o ] = restore ? addPixels ( p [ i ] , v ) : v ; } return ;
392+ case 13 : for ( ; i < end ; i ++ , o ++ ) { const v = clampAddSubtractHalf ( average2 ( p [ i - 1 ] , p [ i - width ] ) , p [ i - width - 1 ] ) ; out [ o ] = restore ? addPixels ( p [ i ] , v ) : v ; } return ;
393+ // mode 0, and modes 14 and 15 (not defined, libwebp predicts black like mode 0)
394+ default : for ( ; i < end ; i ++ , o ++ ) out [ o ] = restore ? addPixels ( p [ i ] , 0xff000000 ) : 0xff000000 ;
391395 }
392396}
393397
@@ -403,10 +407,10 @@ function inversePredictor(p: Uint32Array, width: number, height: number, bits: n
403407 // first column: top
404408 p [ row ] = addPixels ( p [ row ] , p [ row - width ] ) ;
405409 const modeRow = ( y >> bits ) * blocksPerRow ;
406- for ( let x = 1 ; x < width ; x ++ ) {
407- const mode = modes [ modeRow + ( x >> bits ) ] >> 8 & 15 ;
408- const i = row + x ;
409- p [ i ] = addPixels ( p [ i ] , predict ( mode , p , i , width ) ) ;
410+ for ( let x = 1 ; x < width ; ) {
411+ const end = Math . min ( width , ( x >> bits ) + 1 << bits ) ;
412+ predictRun ( modes [ modeRow + ( x >> bits ) ] >> 8 & 15 , p , row + x , end - x , width , p , row + x ) ;
413+ x = end ;
410414 }
411415 }
412416}
@@ -780,10 +784,12 @@ function tokenize(pixels: Uint32Array, width: number, cacheBits: number): Stream
780784 const HASH_BITS = 15 , CHAIN = 24 , MAX_LENGTH = 4096 , MIN_LENGTH = 3 , WINDOW = ( 1 << 20 ) - 120 ;
781785 const head = new Int32Array ( 1 << HASH_BITS ) . fill ( - 1 ) ;
782786 const chain = new Int32Array ( n ) ;
783- const hash = ( i : number ) : number => Math . imul ( Math . imul ( pixels [ i ] , 0x1e35a7bd ) ^ pixels [ i + 1 ] , 0x9e3779b1 ) >>> 32 - HASH_BITS ;
787+ // hashes of the pixel pairs starting at every index (but the last)
788+ const hashes = new Int32Array ( n ) ;
789+ for ( let i = 0 ; i + 1 < n ; i ++ ) hashes [ i ] = Math . imul ( Math . imul ( pixels [ i ] , 0x1e35a7bd ) ^ pixels [ i + 1 ] , 0x9e3779b1 ) >>> 32 - HASH_BITS ;
784790 const insert = ( i : number ) : void => {
785791 if ( i + 1 >= n ) return ;
786- const h = hash ( i ) ;
792+ const h = hashes [ i ] ;
787793 chain [ i ] = head [ h ] ;
788794 head [ h ] = i ;
789795 } ;
@@ -811,7 +817,7 @@ function tokenize(pixels: Uint32Array, width: number, cacheBits: number): Stream
811817 if ( l > bestLength ) { bestLength = l ; bestDistance = width ; }
812818 }
813819 if ( i + 1 < n && bestLength < MAX_LENGTH ) {
814- for ( let j = head [ hash ( i ) ] , k = 0 ; j >= 0 && k < CHAIN && i - j <= WINDOW ; j = chain [ j ] , k ++ ) {
820+ for ( let j = head [ hashes [ i ] ] , k = 0 ; j >= 0 && k < CHAIN && i - j <= WINDOW ; j = chain [ j ] , k ++ ) {
815821 // the chain goes from near to far: a candidate no closer than the best one only wins by being
816822 // longer, which it can't be if it differs right after the best length
817823 if ( i - j >= bestDistance && ( i + bestLength >= n || pixels [ j + bestLength ] !== pixels [ i + bestLength ] ) ) continue ;
@@ -901,11 +907,11 @@ function writeImage(w: BitWriter, pixels: Uint32Array, width: number, isMain: bo
901907// subtracts two ARGB pixels per channel, modulo 256
902908const subPixels = ( a : number , b : number ) : number => ( ( a | 0x00ff00ff ) - ( b & 0xff00ff00 ) & 0xff00ff00 | ( a | 0xff00ff00 ) - ( b & 0x00ff00ff ) & 0x00ff00ff ) >>> 0 ;
903909
904- // cost estimate of a residual: sum of the channels' distances to zero (as signed bytes )
905- function residualCost ( r : number ) : number {
906- const c0 = r & 255 , c1 = r >>> 8 & 255 , c2 = r >>> 16 & 255 , c3 = r >>> 24 ;
907- return ( c0 < 128 ? c0 : 256 - c0 ) + ( c1 < 128 ? c1 : 256 - c1 ) + ( c2 < 128 ? c2 : 256 - c2 ) + ( c3 < 128 ? c3 : 256 - c3 ) ;
908- }
910+ // distance of a residual byte to zero (as a signed byte )
911+ const BYTE_COST = Uint8Array . from ( { length : 256 } , ( _ , c ) => c < 128 ? c : 256 - c ) ;
912+
913+ // cost estimate of a residual: sum of the channels' distances to zero
914+ const residualCost = ( r : number ) : number => BYTE_COST [ r & 255 ] + BYTE_COST [ r >>> 8 & 255 ] + BYTE_COST [ r >>> 16 & 255 ] + BYTE_COST [ r >>> 24 ] ;
909915
910916/**
911917 * Picks a predictor per block and replaces the pixels with their residuals
@@ -916,30 +922,37 @@ function applyPredictor(p: Uint32Array, width: number, height: number, bits: num
916922 const modes = new Uint32Array ( blocksX * blocksY ) ;
917923 const size = 1 << bits ;
918924
919- // predictions use the original pixels, so modes are chosen and residuals computed on a copy
920- const src = p . slice ( ) ;
921- const predictAt = ( mode : number , i : number , x : number , y : number ) : number =>
922- y === 0 ? ( x === 0 ? 0xff000000 : src [ i - 1 ] ) : x === 0 ? src [ i - width ] : predict ( mode , src , i , width ) ;
925+ // predictions use the original pixels, so modes are chosen and residuals computed on a copy. A block's rows are
926+ // predicted into `predicted` (the first row from the left, the first column from the top, whatever the mode)
927+ const src = p . slice ( ) , predicted = new Uint32Array ( size ) ;
928+ const predictRow = ( mode : number , x0 : number , x1 : number , y : number ) : void => {
929+ const row = y * width ;
930+ if ( y === 0 ) {
931+ for ( let x = x0 ; x < x1 ; x ++ ) predicted [ x - x0 ] = x === 0 ? 0xff000000 : src [ row + x - 1 ] ;
932+ return ;
933+ }
934+ let x = x0 ;
935+ if ( x === 0 ) predicted [ x ++ ] = src [ row - width ] ;
936+ predictRun ( mode , src , row + x , x1 - x , width , predicted , x - x0 ) ;
937+ } ;
923938
924939 for ( let by = 0 ; by < blocksY ; by ++ )
925940 for ( let bx = 0 ; bx < blocksX ; bx ++ ) {
926941 let best = 0 , bestCost = Infinity ;
927942 const x0 = bx * size , y0 = by * size , x1 = Math . min ( width , x0 + size ) , y1 = Math . min ( height , y0 + size ) ;
928943 for ( let mode = 0 ; mode < 14 ; mode ++ ) {
929944 let cost = 0 ;
930- for ( let y = y0 ; y < y1 && cost < bestCost ; y ++ )
931- for ( let x = x0 ; x < x1 ; x ++ ) {
932- const i = y * width + x ;
933- cost += residualCost ( subPixels ( src [ i ] , predictAt ( mode , i , x , y ) ) ) ;
934- }
945+ for ( let y = y0 ; y < y1 && cost < bestCost ; y ++ ) {
946+ predictRow ( mode , x0 , x1 , y ) ;
947+ for ( let x = x0 , i = y * width + x0 ; x < x1 ; x ++ , i ++ ) cost += residualCost ( subPixels ( src [ i ] , predicted [ x - x0 ] ) ) ;
948+ }
935949 if ( cost < bestCost ) { bestCost = cost ; best = mode ; }
936950 }
937951 modes [ by * blocksX + bx ] = ( 0xff000000 | best << 8 ) >>> 0 ;
938- for ( let y = y0 ; y < y1 ; y ++ )
939- for ( let x = x0 ; x < x1 ; x ++ ) {
940- const i = y * width + x ;
941- p [ i ] = subPixels ( src [ i ] , predictAt ( best , i , x , y ) ) ;
942- }
952+ for ( let y = y0 ; y < y1 ; y ++ ) {
953+ predictRow ( best , x0 , x1 , y ) ;
954+ for ( let x = x0 , i = y * width + x0 ; x < x1 ; x ++ , i ++ ) p [ i ] = subPixels ( src [ i ] , predicted [ x - x0 ] ) ;
955+ }
943956 }
944957
945958 return modes ;
0 commit comments