@@ -670,78 +670,85 @@ function buildPlane(component: Component): { plane: Uint8Array, stride: number }
670670 return { plane, stride } ;
671671}
672672
673- // upsamples a component to full resolution, following libjpeg's fancy upsampling
674- function upsample ( component : Component , frame : Frame ) : { samples : Uint8Array , stride : number } {
673+ // a component at full resolution, following libjpeg's fancy upsampling: the plane itself if it isn't subsampled, else
674+ // a row buffer that fill(y) upsamples output row y into
675+ interface Samples {
676+ samples : Uint8Array ;
677+ stride : number ;
678+ fill ?: ( y : number ) => void ;
679+ }
680+
681+ function upsample ( component : Component , frame : Frame ) : Samples {
675682 const { plane, stride } = buildPlane ( component ) ;
676683 const hf = frame . hmax / component . h ;
677684 const vf = frame . vmax / component . v ;
678685 const w = component . width , h = component . height ;
679- const W = Math . ceil ( w * hf ) , H = Math . ceil ( h * vf ) ;
680- const out = new Uint8Array ( W * H ) ;
686+ const W = Math . ceil ( w * hf ) ;
687+ const out = new Uint8Array ( W ) ;
681688 const fancy = w > 2 ;
682689
683- if ( hf === 1 && vf === 1 ) {
684- for ( let y = 0 ; y < h ; y ++ ) out . set ( plane . subarray ( y * stride , y * stride + w ) , y * W ) ;
685- } else if ( hf === 2 && vf === 1 && fancy ) {
686- for ( let y = 0 ; y < h ; y ++ ) {
687- const i = y * stride , o = y * W ;
688- out [ o ] = plane [ i ] ;
689- out [ o + 1 ] = plane [ i ] * 3 + plane [ i + 1 ] + 2 >> 2 ;
690+ if ( hf === 1 && vf === 1 ) return { samples : plane , stride } ;
691+
692+ if ( hf === 2 && vf === 1 && fancy ) return {
693+ samples : out , stride : 0 , fill : y => {
694+ const i = y * stride ;
695+ out [ 0 ] = plane [ i ] ;
696+ out [ 1 ] = plane [ i ] * 3 + plane [ i + 1 ] + 2 >> 2 ;
690697 for ( let x = 1 ; x < w - 1 ; x ++ ) {
691698 const v = plane [ i + x ] * 3 ;
692- out [ o + 2 * x ] = v + plane [ i + x - 1 ] + 1 >> 2 ;
693- out [ o + 2 * x + 1 ] = v + plane [ i + x + 1 ] + 2 >> 2 ;
699+ out [ 2 * x ] = v + plane [ i + x - 1 ] + 1 >> 2 ;
700+ out [ 2 * x + 1 ] = v + plane [ i + x + 1 ] + 2 >> 2 ;
694701 }
695- out [ o + 2 * w - 2 ] = plane [ i + w - 1 ] * 3 + plane [ i + w - 2 ] + 1 >> 2 ;
696- out [ o + 2 * w - 1 ] = plane [ i + w - 1 ] ;
702+ out [ 2 * w - 2 ] = plane [ i + w - 1 ] * 3 + plane [ i + w - 2 ] + 1 >> 2 ;
703+ out [ 2 * w - 1 ] = plane [ i + w - 1 ] ;
697704 }
698- } else if ( hf === 1 && vf === 2 && fancy ) {
699- for ( let y = 0 ; y < h ; y ++ ) {
700- const i = y * stride ;
701- const above = Math . max ( 0 , y - 1 ) * stride ;
702- const below = Math . min ( h - 1 , y + 1 ) * stride ;
703- for ( let x = 0 ; x < w ; x ++ ) {
704- const v = plane [ i + x ] * 3 ;
705- out [ 2 * y * W + x ] = v + plane [ above + x ] + 1 >> 2 ;
706- out [ ( 2 * y + 1 ) * W + x ] = v + plane [ below + x ] + 2 >> 2 ;
705+ } ;
706+
707+ if ( hf === 1 && vf === 2 && fancy ) return {
708+ samples : out , stride : 0 , fill : y => {
709+ const i = ( y >> 1 ) * stride ;
710+ if ( y & 1 ) {
711+ const below = Math . min ( h - 1 , ( y >> 1 ) + 1 ) * stride ;
712+ for ( let x = 0 ; x < w ; x ++ ) out [ x ] = plane [ i + x ] * 3 + plane [ below + x ] + 2 >> 2 ;
713+ } else {
714+ const above = Math . max ( 0 , ( y >> 1 ) - 1 ) * stride ;
715+ for ( let x = 0 ; x < w ; x ++ ) out [ x ] = plane [ i + x ] * 3 + plane [ above + x ] + 1 >> 2 ;
707716 }
708717 }
709- } else if ( hf === 2 && vf === 2 && fancy ) {
710- for ( let y = 0 ; y < h ; y ++ ) {
711- const i = y * stride ;
712-
713- for ( let pass = 0 ; pass < 2 ; pass ++ ) {
714- const other = ( pass === 0 ? Math . max ( 0 , y - 1 ) : Math . min ( h - 1 , y + 1 ) ) * stride ;
715- const o = ( 2 * y + pass ) * W ;
718+ } ;
716719
717- let thisSum = plane [ i ] * 3 + plane [ other ] ;
718- let nextSum = plane [ i + 1 ] * 3 + plane [ other + 1 ] ;
719- out [ o ] = thisSum * 4 + 8 >> 4 ;
720- out [ o + 1 ] = thisSum * 3 + nextSum + 7 >> 4 ;
721- let lastSum = thisSum ;
722- thisSum = nextSum ;
720+ if ( hf === 2 && vf === 2 && fancy ) return {
721+ samples : out , stride : 0 , fill : y => {
722+ const i = ( y >> 1 ) * stride ;
723+ const other = ( y & 1 ? Math . min ( h - 1 , ( y >> 1 ) + 1 ) : Math . max ( 0 , ( y >> 1 ) - 1 ) ) * stride ;
723724
724- for ( let x = 1 ; x < w - 1 ; x ++ ) {
725- nextSum = plane [ i + x + 1 ] * 3 + plane [ other + x + 1 ] ;
726- out [ o + 2 * x ] = thisSum * 3 + lastSum + 8 >> 4 ;
727- out [ o + 2 * x + 1 ] = thisSum * 3 + nextSum + 7 >> 4 ;
728- lastSum = thisSum ;
729- thisSum = nextSum ;
730- }
725+ let thisSum = plane [ i ] * 3 + plane [ other ] ;
726+ let nextSum = plane [ i + 1 ] * 3 + plane [ other + 1 ] ;
727+ out [ 0 ] = thisSum * 4 + 8 >> 4 ;
728+ out [ 1 ] = thisSum * 3 + nextSum + 7 >> 4 ;
729+ let lastSum = thisSum ;
730+ thisSum = nextSum ;
731731
732- out [ o + 2 * w - 2 ] = thisSum * 3 + lastSum + 8 >> 4 ;
733- out [ o + 2 * w - 1 ] = thisSum * 4 + 7 >> 4 ;
732+ for ( let x = 1 ; x < w - 1 ; x ++ ) {
733+ nextSum = plane [ i + x + 1 ] * 3 + plane [ other + x + 1 ] ;
734+ out [ 2 * x ] = thisSum * 3 + lastSum + 8 >> 4 ;
735+ out [ 2 * x + 1 ] = thisSum * 3 + nextSum + 7 >> 4 ;
736+ lastSum = thisSum ;
737+ thisSum = nextSum ;
734738 }
739+
740+ out [ 2 * w - 2 ] = thisSum * 3 + lastSum + 8 >> 4 ;
741+ out [ 2 * w - 1 ] = thisSum * 4 + 7 >> 4 ;
735742 }
736- } else {
737- // plain replication for every other sampling ratio
738- for ( let y = 0 ; y < H ; y ++ ) {
743+ } ;
744+
745+ // plain replication for every other sampling ratio
746+ return {
747+ samples : out , stride : 0 , fill : y => {
739748 const i = Math . min ( h - 1 , y / vf | 0 ) * stride ;
740- for ( let x = 0 ; x < W ; x ++ ) out [ y * W + x ] = plane [ i + Math . min ( w - 1 , x / hf | 0 ) ] ;
749+ for ( let x = 0 ; x < W ; x ++ ) out [ x ] = plane [ i + Math . min ( w - 1 , x / hf | 0 ) ] ;
741750 }
742- }
743-
744- return { samples : out , stride : W } ;
751+ } ;
745752}
746753
747754// libjpeg's fixed point YCbCr -> RGB tables
@@ -787,6 +794,7 @@ function output(frame: Frame, adobe: { transform: number } | null, jfif: boolean
787794 // (YCbCr a pixel at a time as a 32-bit word, little endian)
788795 const words = littleEndian ? new Uint32Array ( pixels . buffer ) : null ;
789796 for ( let y = 0 , o = 0 ; y < height ; y ++ , o = y * width * 4 ) {
797+ for ( const p of planes ) p . fill ?.( y ) ;
790798 const r0 = y * p0 . stride , r1 = p1 ? y * p1 . stride : r0 , r2 = p2 ? y * p2 . stride : r0 ;
791799 if ( grey ) for ( let x = 0 ; x < width ; x ++ , o += 4 ) {
792800 pixels [ o ] = pixels [ o + 1 ] = pixels [ o + 2 ] = s0 [ r0 + x ] ;
@@ -811,6 +819,7 @@ function output(frame: Frame, adobe: { transform: number } | null, jfif: boolean
811819 }
812820
813821 for ( let y = 0 , o = 0 ; y < height ; y ++ ) {
822+ for ( const p of planes ) p . fill ?.( y ) ;
814823 const r0 = y * p0 . stride , r1 = y * p1 . stride , r2 = y * p2 . stride , r3 = y * p3 . stride ;
815824
816825 for ( let x = 0 ; x < width ; x ++ , o += 4 ) {
0 commit comments