@@ -28,6 +28,30 @@ export interface GIFData {
2828 frames : GIFFrame [ ] ;
2929}
3030
31+ /** A decoded frame: color indices (in display order) and what they stand for */
32+ export interface DecodedGIFFrame {
33+ x : number ;
34+ y : number ;
35+ width : number ;
36+ height : number ;
37+ /** Frame delay in centiseconds */
38+ delay : number ;
39+ /** ImageScript disposal mode (0 any, 1 keep, 2 previous, 3 background) */
40+ dispose : number ;
41+ indices : Uint8Array ;
42+ /** RGBA of every index as a 32-bit word (indices past the color table are opaque black) */
43+ colors : Uint32Array ;
44+ /** -1 if none */
45+ transparentIndex : number ;
46+ }
47+
48+ export interface DecodedGIF {
49+ width : number ;
50+ height : number ;
51+ loopCount : number ;
52+ frames : DecodedGIFFrame [ ] ;
53+ }
54+
3155interface GraphicControl {
3256 dispose : number ;
3357 delay : number ;
@@ -182,7 +206,7 @@ function writeSubBlocks(out: ByteWriter, data: Uint8Array): void {
182206 * @param {boolean } [onlyFirstFrame=false]
183207 * @return {{width: number, height: number, loopCount: number, frames: GIFFrame[]} }
184208 */
185- export function decode ( data : Uint8Array , onlyFirstFrame = false ) : GIFData {
209+ export function decode ( data : Uint8Array , onlyFirstFrame = false ) : DecodedGIF {
186210 const reader = new ByteReader ( data ) ;
187211
188212 const signature = reader . string ( 6 ) ;
@@ -195,7 +219,7 @@ export function decode(data: Uint8Array, onlyFirstFrame = false): GIFData {
195219
196220 const globalTable = flags & 0x80 ? reader . slice ( 3 << ( flags & 7 ) + 1 ) : null ;
197221
198- const frames : GIFFrame [ ] = [ ] ;
222+ const frames : DecodedGIFFrame [ ] = [ ] ;
199223 let loopCount = 0 ;
200224 let control : GraphicControl | null = null ;
201225
@@ -269,41 +293,38 @@ export function decode(data: Uint8Array, onlyFirstFrame = false): GIFData {
269293 control = null ;
270294 if ( ! fw || ! fh ) continue ;
271295
272- const pixels = new Uint8Array ( fw * fh * 4 ) ;
273- const rows = interlaced ? interlacedRows ( fh ) : null ;
274-
275- // the RGBA value of every index: transparent stays 0, indices past the color table are opaque black
276- const colors = new Uint8Array ( 256 * 4 ) ;
296+ // the RGBA value of every index: indices past the color table are opaque black (the transparent one is
297+ // skipped when compositing)
298+ const colors = new Uint32Array ( 256 ) , bytes = new Uint8Array ( colors . buffer ) ;
277299 for ( let index = 0 ; index < 256 ; index ++ ) {
278- if ( index === transparentIndex ) continue ;
279- if ( table && index * 3 + 2 < table . length ) colors . set ( table . subarray ( index * 3 , index * 3 + 3 ) , index * 4 ) ;
280- colors [ index * 4 + 3 ] = 0xff ;
300+ if ( table && index * 3 + 2 < table . length ) bytes . set ( table . subarray ( index * 3 , index * 3 + 3 ) , index * 4 ) ;
301+ bytes [ index * 4 + 3 ] = 0xff ;
281302 }
282- const lookup = new Uint32Array ( colors . buffer ) , out = new Uint32Array ( pixels . buffer ) ;
283303
284- for ( let row = 0 ; row < fh ; row ++ ) {
285- const target = ( rows ? rows [ row ] : row ) * fw , source = row * fw ;
286- for ( let column = 0 ; column < fw ; column ++ ) out [ target + column ] = lookup [ indices [ source + column ] ] ;
304+ let ordered = indices ;
305+ if ( interlaced ) {
306+ const rows = interlacedRows ( fh ) ;
307+ ordered = new Uint8Array ( indices . length ) ;
308+ for ( let row = 0 ; row < fh ; row ++ ) ordered . set ( indices . subarray ( row * fw , ( row + 1 ) * fw ) , rows [ row ] * fw ) ;
287309 }
288310
289- frames . push ( { x, y, width : fw , height : fh , delay, dispose, pixels } ) ;
311+ frames . push ( { x, y, width : fw , height : fh , delay, dispose, indices : ordered , colors , transparentIndex } ) ;
290312 if ( onlyFirstFrame ) break ;
291313 }
292314 }
293315}
294316
295317/**
296318 * Composites decoded frames onto the logical screen, applying their disposal modes
297- * @param {{width: number, height: number, frames: GIFFrame[]} } gif output of {@link decode}
298- * @return {Array<{pixels: Uint8Array, delay: number}> } full screen RGBA frames
319+ * @return full screen RGBA frames
299320 */
300- export function composite ( { width, height, frames } : GIFData ) : Array < { pixels : Uint8Array , delay : number } > {
321+ export function composite ( { width, height, frames } : DecodedGIF ) : Array < { pixels : Uint8Array , delay : number } > {
301322 const canvas = new Uint32Array ( width * height ) ;
302323 const output = [ ] ;
303324
304325 for ( const frame of frames ) {
305326 const previous = frame . dispose === 2 ? canvas . slice ( ) : null ;
306- const src = new Uint32Array ( frame . pixels . buffer , frame . pixels . byteOffset , frame . width * frame . height ) ;
327+ const { indices , colors , transparentIndex } = frame ;
307328
308329 // clip the frame to the logical screen
309330 const x0 = Math . min ( frame . x , width ) , x1 = Math . min ( frame . x + frame . width , width ) ;
@@ -313,8 +334,11 @@ export function composite({ width, height, frames }: GIFData): Array<{ pixels: U
313334 const srcRow = ( y - frame . y ) * frame . width - frame . x ;
314335 const dstRow = y * width ;
315336
316- for ( let x = x0 ; x < x1 ; x ++ )
317- if ( frame . pixels [ ( srcRow + x ) * 4 + 3 ] !== 0 ) canvas [ dstRow + x ] = src [ srcRow + x ] ;
337+ if ( transparentIndex < 0 ) for ( let x = x0 ; x < x1 ; x ++ ) canvas [ dstRow + x ] = colors [ indices [ srcRow + x ] ] ;
338+ else for ( let x = x0 ; x < x1 ; x ++ ) {
339+ const index = indices [ srcRow + x ] ;
340+ if ( index !== transparentIndex ) canvas [ dstRow + x ] = colors [ index ] ;
341+ }
318342 }
319343
320344 output . push ( { pixels : new Uint8Array ( canvas . slice ( ) . buffer ) , delay : frame . delay } ) ;
0 commit comments