@@ -9,6 +9,7 @@ import { ByteWriter } from '../util/bytes.ts';
99import { MAX_PIXELS , checkPixels } from '../util/limits.ts' ;
1010import { orientation as exifOrientation } from './exif.ts' ;
1111import { concat } from '../util/mem.ts' ;
12+ import { littleEndian } from '../util/alpha.ts' ;
1213
1314// natural order index of the n-th zigzag coefficient
1415const ZIGZAG = Int32Array . of (
@@ -747,6 +748,11 @@ for (let i = 0; i < 256; i++) {
747748 cbG [ i ] = - 22554 * x + 32768 ;
748749}
749750
751+ // clamping to 0-255 by a lookup: CLAMP[v + CLAMP_OFFSET] for v in -CLAMP_OFFSET..CLAMP_OFFSET + 255 (YCbCr to RGB
752+ // stays within 227 of the range)
753+ const CLAMP_OFFSET = 256 , CLAMP = new Uint8Array ( 768 ) ;
754+ for ( let i = 0 ; i < 768 ; i ++ ) CLAMP [ i ] = clamp8 ( i - CLAMP_OFFSET ) ;
755+
750756function output ( frame : Frame , adobe : { transform : number } | null , jfif : boolean , override : DecodeOptions [ 'transform' ] ) : Uint8Array {
751757 const { width, height, components } = frame ;
752758 const planes = components . map ( component => upsample ( component , frame ) ) ;
@@ -765,33 +771,43 @@ function output(frame: Frame, adobe: { transform: number } | null, jfif: boolean
765771
766772 const [ p0 , p1 , p2 , p3 ] = planes ;
767773
774+ // one loop per transform: deciding per pixel costs more than the conversion
775+ if ( transform === 'grey' || transform === 'rgb' || transform === 'ycc' ) {
776+ const s0 = p0 . samples , s1 = ( p1 ?? p0 ) . samples , s2 = ( p2 ?? p0 ) . samples ;
777+ const grey = transform === 'grey' , ycc = transform === 'ycc' ;
778+ // (YCbCr a pixel at a time as a 32-bit word, little endian)
779+ const words = littleEndian ? new Uint32Array ( pixels . buffer ) : null ;
780+ for ( let y = 0 , o = 0 ; y < height ; y ++ , o = y * width * 4 ) {
781+ const r0 = y * p0 . stride , r1 = p1 ? y * p1 . stride : r0 , r2 = p2 ? y * p2 . stride : r0 ;
782+ if ( grey ) for ( let x = 0 ; x < width ; x ++ , o += 4 ) {
783+ pixels [ o ] = pixels [ o + 1 ] = pixels [ o + 2 ] = s0 [ r0 + x ] ;
784+ pixels [ o + 3 ] = 0xff ;
785+ } else if ( ycc && words ) for ( let x = 0 , w = o >> 2 ; x < width ; x ++ , w ++ ) {
786+ const a = s0 [ r0 + x ] + CLAMP_OFFSET , b = s1 [ r1 + x ] , c = s2 [ r2 + x ] ;
787+ words [ w ] = ( CLAMP [ a + crR [ c ] ] | CLAMP [ a + ( cbG [ b ] + crG [ c ] >> 16 ) ] << 8 | CLAMP [ a + cbB [ b ] ] << 16 | 0xff000000 ) >>> 0 ;
788+ } else if ( ycc ) for ( let x = 0 ; x < width ; x ++ , o += 4 ) {
789+ const a = s0 [ r0 + x ] + CLAMP_OFFSET , b = s1 [ r1 + x ] , c = s2 [ r2 + x ] ;
790+ pixels [ o ] = CLAMP [ a + crR [ c ] ] ;
791+ pixels [ o + 1 ] = CLAMP [ a + ( cbG [ b ] + crG [ c ] >> 16 ) ] ;
792+ pixels [ o + 2 ] = CLAMP [ a + cbB [ b ] ] ;
793+ pixels [ o + 3 ] = 0xff ;
794+ } else for ( let x = 0 ; x < width ; x ++ , o += 4 ) {
795+ pixels [ o ] = s0 [ r0 + x ] ;
796+ pixels [ o + 1 ] = s1 [ r1 + x ] ;
797+ pixels [ o + 2 ] = s2 [ r2 + x ] ;
798+ pixels [ o + 3 ] = 0xff ;
799+ }
800+ }
801+ return pixels ;
802+ }
803+
768804 for ( let y = 0 , o = 0 ; y < height ; y ++ ) {
769- const r0 = y * p0 . stride , r1 = p1 ? y * p1 . stride : 0 , r2 = p2 ? y * p2 . stride : 0 , r3 = p3 ? y * p3 . stride : 0 ;
805+ const r0 = y * p0 . stride , r1 = y * p1 . stride , r2 = y * p2 . stride , r3 = y * p3 . stride ;
770806
771807 for ( let x = 0 ; x < width ; x ++ , o += 4 ) {
772808 pixels [ o + 3 ] = 0xff ;
773-
774- if ( transform === 'grey' ) {
775- pixels [ o ] = pixels [ o + 1 ] = pixels [ o + 2 ] = p0 . samples [ r0 + x ] ;
776- continue ;
777- }
778-
779809 const a = p0 . samples [ r0 + x ] , b = p1 . samples [ r1 + x ] , c = p2 . samples [ r2 + x ] ;
780810
781- if ( transform === 'rgb' ) {
782- pixels [ o ] = a ;
783- pixels [ o + 1 ] = b ;
784- pixels [ o + 2 ] = c ;
785- continue ;
786- }
787-
788- if ( transform === 'ycc' ) {
789- pixels [ o ] = clamp8 ( a + crR [ c ] ) ;
790- pixels [ o + 1 ] = clamp8 ( a + ( cbG [ b ] + crG [ c ] >> 16 ) ) ;
791- pixels [ o + 2 ] = clamp8 ( a + cbB [ b ] ) ;
792- continue ;
793- }
794-
795811 let cyan = a , magenta = b , yellow = c , k = p3 . samples [ r3 + x ] ;
796812 if ( transform === 'ycck' ) {
797813 cyan = 255 - clamp8 ( a + crR [ c ] ) ;
@@ -922,11 +938,12 @@ export function encode(pixels: Uint8Array, width: number, height: number, qualit
922938 { plane : subsample ? downsample ( Cr ) : Cr , stride : chromaStride , h : 1 , v : 1 , table : 1 }
923939 ] ;
924940
925- // quantized blocks in MCU order (Y blocks, then Cb, then Cr)
941+ // quantized blocks in MCU order (Y blocks, then Cb, then Cr), their coefficients in zigzag order, with the index
942+ // of each block's last nonzero coefficient
926943 const blocksPerMcu = hmax * hmax + 2 ;
927944 const count = mcusPerLine * mcusPerColumn * blocksPerMcu ;
928945 const coefficients = new Int16Array ( count * 64 ) ;
929- const owners = new Uint8Array ( count ) ;
946+ const owners = new Uint8Array ( count ) , ends = new Uint8Array ( count ) ;
930947 const block = new Float64Array ( 64 ) ;
931948 let n = 0 ;
932949
@@ -948,12 +965,18 @@ export function encode(pixels: Uint8Array, width: number, height: number, qualit
948965 fdct ( block ) ;
949966 const o = n * 64 ;
950967 // round half up like libjpeg, without Math.round
951- for ( let i = 0 ; i < 64 ; i ++ ) coefficients [ o + i ] = ( block [ i ] * d [ i ] + 16384.5 | 0 ) - 16384 ;
968+ let end = 0 ;
969+ for ( let k = 0 ; k < 64 ; k ++ ) {
970+ const i = ZIGZAG [ k ] , value = ( block [ i ] * d [ i ] + 16384.5 | 0 ) - 16384 ;
971+ coefficients [ o + k ] = value ;
972+ if ( value ) end = k ;
973+ }
952974 owners [ n ] = c ;
975+ ends [ n ] = end ;
953976 }
954977 }
955978
956- const blocks = { coefficients, owners, count } ;
979+ const blocks = { coefficients, owners, ends , count } ;
957980
958981 // pass 1: gather symbol statistics, pass 2: write with optimal tables
959982 const frequencies = [ new Int32Array ( 257 ) , new Int32Array ( 257 ) , new Int32Array ( 257 ) , new Int32Array ( 257 ) ] ;
@@ -1010,10 +1033,11 @@ export function encode(pixels: Uint8Array, width: number, height: number, qualit
10101033interface Blocks {
10111034 coefficients : Int16Array ;
10121035 owners : Uint8Array ;
1036+ ends : Uint8Array ;
10131037 count : number ;
10141038}
10151039
1016- function encodeBlocks ( { coefficients, owners, count } : Blocks , symbol : ( table : number , symbol : number ) => void , bits : ( value : number , size : number ) => void ) : void {
1040+ function encodeBlocks ( { coefficients, owners, ends , count } : Blocks , symbol : ( table : number , symbol : number ) => void , bits : ( value : number , size : number ) => void ) : void {
10171041 const pred = [ 0 , 0 , 0 ] ;
10181042
10191043 for ( let n = 0 ; n < count ; n ++ ) {
@@ -1029,8 +1053,8 @@ function encodeBlocks({ coefficients, owners, count }: Blocks, symbol: (table: n
10291053 if ( size ) bits ( diff < 0 ? diff - 1 & ( 1 << size ) - 1 : diff , size ) ;
10301054
10311055 let run = 0 ;
1032- for ( let k = 1 ; k < 64 ; k ++ ) {
1033- const value = coefficients [ o + ZIGZAG [ k ] ] ;
1056+ for ( let k = 1 , end = ends [ n ] ; k <= end ; k ++ ) {
1057+ const value = coefficients [ o + k ] ;
10341058
10351059 if ( value === 0 ) {
10361060 run ++ ;
@@ -1048,18 +1072,13 @@ function encodeBlocks({ coefficients, owners, count }: Blocks, symbol: (table: n
10481072 run = 0 ;
10491073 }
10501074
1051- if ( run ) symbol ( acTable , 0x00 ) ;
1075+ if ( ends [ n ] < 63 ) symbol ( acTable , 0x00 ) ;
10521076 }
10531077}
10541078
1079+ // the bits a value takes
10551080function magnitude ( value : number ) : number {
1056- value = Math . abs ( value ) ;
1057- let size = 0 ;
1058- while ( value ) {
1059- size ++ ;
1060- value >>= 1 ;
1061- }
1062- return size ;
1081+ return 32 - Math . clz32 ( Math . abs ( value ) ) ;
10631082}
10641083
10651084// libjpeg's jpeg_gen_optimal_table (ITU T.81 Annex K.2)
0 commit comments