import { GifColor } from "./GifColor" ; import { GifFrame } from "./GifFrame" ; import { GifParser } from "./GifParser" ; import { GifVersion } from "./GifVersion" ; import { GifImage } from "./GifImage" ; import { GifCompressedCodesToByteArrayConverter } from "./GifCompressedCodesToByteArrayConverter" ; export class Gif implements GifImage { /**
     * GIF Header block.
     */ private _version : GifVersion ; /**
     * GIF image width.
     */ private _width : number ; /**
     * GIF image height.
     */ private _height : number ; /**
     * The number of color in the Global Color Table.
     */ private _globalTableSize : number ; public colorResolution : number ; public sortFlag : boolean ; public bgColorIndex : number ; public pixelAspectRatio : number ; /**
     * The Global Color Table array.
     */ private _globalColorTable : Uint8Array ; public frames : GifFrame [ ] ; public isLoop : boolean ; /**
     * The number of animation iteration count.
     * if this is 0, it is infinite.
     */ private _loopCount : number ; public frameIndex1 = 0 ; public frameIndex2 = 0 ; /**
     * Gif
     */ public constructor ( skipDefault ? ) { this . frames = [ ] ; if ( skipDefault ) { return ; } this . _version = GifVersion . GIF89a ; this . _width = 1 ; this . _height = 1 ; this . colorResolution = 112 ; this . sortFlag = false ; this . bgColorIndex = 1 ; this . pixelAspectRatio = 0 ; this . globalColorTable = GifColor . createColorTable ( [ new GifColor ( 0 , 0 , 0 ) , new GifColor ( 255 , 255 , 255 ) , ] ) ; } return { Gif } public static parse ( buffer ) { const gif = new Gif ( true ) ; const data = new DataView ( buffer ) ; let offset = GifParser . readHeader ( gif , data ) ; while ( true ) { offset = GifParser . readBlock ( gif , data , offset ) ; if ( offset === - 1 ) { break ; } } return gif ; } public versionName ( ) { return GifVersion [ this . _version ] ; } public get version ( ) : GifVersion { return this . _version ; } public set version ( v ) { this . _version = v ; } public get width ( ) : number { return this . _width ; } public set width ( width ) { width = ~ ~ width ; if ( width > 0xffff || width < 0 ) { throw new RangeError ( "width range error: " + width ) ; } this . _width = width ; } public get height ( ) : number { return this . _height ; } public set height ( height ) { height = ~ ~ height ; if ( height > 0xffff || height < 0 ) { throw new RangeError ( "height range error: " + height ) ; } this . _height = height ; } public get globalColorTable ( ) : Uint8Array { return this . _globalColorTable ; } public set globalColorTable ( bytes ) { this . _globalColorTable = bytes ; this . _globalTableSize = bytes . byteLength / 3 ; if ( this . bgColorIndex === undefined ) { this . bgColorIndex = this . _globalTableSize - 1 ; } } public get globalTableSize ( ) : number { return this . _globalTableSize ; } public get loopCount ( ) : number { return this . _loopCount ; } public set loopCount ( loopCount ) { loopCount = ~ ~ loopCount ; if ( loopCount > 0xffff || loopCount < 0 ) { throw new RangeError ( "loopCount range error: " + loopCount ) ; } this . _loopCount = loopCount ; } public writeToArrayBuffer ( ) { return Gif . writeToArrayBuffer ( this ) ; } return { Uint8Array [ ] } public static writeToArrayBuffer ( gif ) { const output = [ ] ; const header = new DataView ( new ArrayBuffer ( 13 ) ) ; header . setUint8 ( 0 , 71 ) ; header . setUint8 ( 1 , 73 ) ; header . setUint8 ( 2 , 70 ) ; header . setUint8 ( 3 , 56 ) ; if ( gif . version === GifVersion . GIF89a ) { header . setUint8 ( 4 , 57 ) ; } else { header . setUint8 ( 4 , 55 ) ; } header . setUint8 ( 5 , 97 ) ; header . setUint16 ( 6 , gif . width , true ) ; header . setUint16 ( 8 , gif . height , true ) ; let packed = 0 ; let size = gif . globalTableSize ; if ( size > 0 ) { packed |= 128 ; let count = 0 ; do { size = size >> 1 ; ++ count ; } while ( size > 1 ) ; packed |= count - 1 ; } packed |= gif . colorResolution ; if ( gif . sortFlag ) { packed |= 8 ; } header . setUint8 ( 10 , packed ) ; header . setUint8 ( 11 , gif . bgColorIndex ) ; header . setUint8 ( 12 , gif . pixelAspectRatio ) ; output . push ( new Uint8Array ( header . buffer ) ) ; if ( gif . globalTableSize > 0 ) { output . push ( gif . globalColorTable ) ; } if ( gif . isLoop ) { const appExt = new DataView ( new ArrayBuffer ( 19 ) ) ; appExt . setUint8 ( 0 , 0x21 ) ; appExt . setUint8 ( 1 , 0xff ) ; appExt . setUint8 ( 2 , 0x0b ) ; appExt . setUint8 ( 3 , 0x4e ) ; appExt . setUint8 ( 4 , 0x45 ) ; appExt . setUint8 ( 5 , 0x54 ) ; appExt . setUint8 ( 6 , 0x53 ) ; appExt . setUint8 ( 7 , 0x43 ) ; appExt . setUint8 ( 8 , 0x41 ) ; appExt . setUint8 ( 9 , 0x50 ) ; appExt . setUint8 ( 10 , 0x45 ) ; appExt . setUint8 ( 11 , 0x32 ) ; appExt . setUint8 ( 12 , 0x2e ) ; appExt . setUint8 ( 13 , 0x30 ) ; appExt . setUint8 ( 14 , 3 ) ; appExt . setUint8 ( 15 , 1 ) ; appExt . setUint16 ( 16 , gif . loopCount , true ) ; appExt . setUint8 ( 18 , 0 ) ; output . push ( new Uint8Array ( appExt . buffer ) ) ; } gif . frames . forEach ( ( frame ) => { const image = new DataView ( new ArrayBuffer ( 18 ) ) ; image . setUint8 ( 0 , 0x21 ) ; image . setUint8 ( 1 , 0xf9 ) ; image . setUint8 ( 2 , 0x04 ) ; if ( frame . transparentFlag ) { image . setUint8 ( 3 , 1 ) ; } else { image . setUint8 ( 3 , 0 ) ; } image . setUint16 ( 4 , frame . delayCentiSeconds , true ) ; image . setUint8 ( 6 , frame . transparentColorIndex ) ; image . setUint8 ( 7 , 0 ) ; image . setUint8 ( 8 , 0x2c ) ; image . setUint16 ( 9 , frame . x , true ) ; image . setUint16 ( 11 , frame . y , true ) ; image . setUint16 ( 13 , frame . width , true ) ; image . setUint16 ( 15 , frame . height , true ) ; if ( frame . localTableSize > 0 ) { let count = 0 ; let size = frame . localTableSize ; do { size = size >> 1 ; ++ count ; } while ( size > 1 ) ; image . setUint8 ( 17 , 128 | ( count - 1 ) ) ; } else { image . setUint8 ( 17 , 0 ) ; } output . push ( new Uint8Array ( image . buffer ) ) ; if ( frame . localTableSize > 0 ) { output . push ( frame . localColorTable ) ; } output . push ( new Uint8Array ( [ frame . lzwCode ] ) ) ; if ( frame . compressedData === undefined && frame . pixelData === undefined ) { throw new Error ( "no image data" ) ; } let idx = 0 , compressedBytes = frame . compressedData ; if ( compressedBytes instanceof Array ) { compressedBytes = new Uint8Array ( compressedBytes ) ; } else { compressedBytes = compressedBytes || new Uint8Array ( Gif . compressWithLZW ( frame . pixelData , frame . lzwCode ) ) ; } const l = compressedBytes . length ; while ( true ) { if ( l > idx + 255 ) { output . push ( new Uint8Array ( [ 255 ] ) ) ; output . push ( compressedBytes . subarray ( idx , idx + 255 ) ) ; idx += 255 ; continue ; } const bytes = compressedBytes . subarray ( idx ) ; output . push ( new Uint8Array ( [ bytes . byteLength ] ) ) ; output . push ( bytes ) ; break ; } output . push ( new Uint8Array ( [ 0 ] ) ) ; } ) ; output . push ( new Uint8Array ( [ 0x3b ] ) ) ; return output ; } /**
     * TODO
     **/ public writeToArray ( ) { const output = Gif . writeToArrayBuffer ( this ) ; const arr = [ ] ; output . forEach ( ( buffer ) => { for ( let i = 0 , l = buffer . byteLength ; i < l ; ++ i ) { arr . push ( buffer [ i ] ) ; } } ) ; return arr ; } return { Gif [ ] } public split ( overwrite ) { const res = [ ] ; if ( overwrite ) { this . frames . forEach ( ( frame , index ) => { const gif = new Gif ( ) ; gif . version = this . _version ; gif . width = this . _width ; gif . height = this . _height ; gif . colorResolution = this . colorResolution ; gif . sortFlag = this . sortFlag ; gif . bgColorIndex = this . bgColorIndex ; gif . pixelAspectRatio = this . pixelAspectRatio ; gif . globalColorTable = this . _globalColorTable ; if ( index !== 0 && frame . transparentFlag ) { if ( frame . pixelData === undefined ) { frame . decompress ( ) ; } if ( this . frames [ index - 1 ] . pixelData === undefined ) { this . frames [ index - 1 ] . decompress ( ) ; } let edited = false ; for ( let i = 0 , l = frame . pixelData . length ; i < l ; ++ i ) { if ( frame . pixelData [ i ] === frame . transparentColorIndex ) { frame . pixelData [ i ] = this . frames [ index - 1 ] . pixelData [ i ] ; edited = true ; } } if ( edited ) { const compressedBytes = Gif . compressWithLZW ( frame . pixelData , frame . lzwCode ) ; frame . compressedData = new Uint8Array ( compressedBytes ) ; } delete frame . pixelData ; } gif . frames = [ frame ] ; res . push ( gif ) ; } ) ; } else { this . frames . forEach ( ( frame ) => { const gif = new Gif ( ) ; gif . version = this . _version ; gif . width = this . _width ; gif . height = this . _height ; gif . colorResolution = this . colorResolution ; gif . sortFlag = this . sortFlag ; gif . bgColorIndex = this . bgColorIndex ; gif . pixelAspectRatio = this . pixelAspectRatio ; gif . globalColorTable = this . _globalColorTable ; gif . frames = [ frame ] ; res . push ( gif ) ; } ) ; } return res ; } return { Gif } public playback ( overwrite ) { const res = new Gif ( ) ; if ( overwrite ) { this . frames . forEach ( ( frame , index ) => { if ( index !== 0 && frame . transparentFlag ) { if ( frame . pixelData === undefined ) { frame . decompress ( ) ; } if ( this . frames [ index - 1 ] . pixelData === undefined ) { this . frames [ index - 1 ] . decompress ( ) ; } let edited = false ; for ( let i = 0 , l = frame . pixelData . length ; i < l ; ++ i ) { if ( frame . pixelData [ i ] === frame . transparentColorIndex ) { frame . pixelData [ i ] = this . frames [ index - 1 ] . pixelData [ i ] ; edited = true ; } } if ( edited ) { const compressedBytes = Gif . compressWithLZW ( frame . pixelData , frame . lzwCode ) ; frame . compressedData = new Uint8Array ( compressedBytes ) ; } delete frame . pixelData ; } } ) ; } res . version = this . _version ; res . width = this . _width ; res . height = this . _height ; res . colorResolution = this . colorResolution ; res . sortFlag = this . sortFlag ; res . bgColorIndex = this . bgColorIndex ; res . pixelAspectRatio = this . pixelAspectRatio ; res . globalColorTable = this . _globalColorTable ; res . frames = this . frames . reverse ( ) ; res . isLoop = this . isLoop ; res . loopCount = this . _loopCount ; return res ; } public static compressWithLZW ( actualCodes , numBits ) { const bb = new GifCompressedCodesToByteArrayConverter ( ) ; const clearCode = 1 << numBits ; const endOfInfoCode = clearCode + 1 ; let nextCode = endOfInfoCode + 1 ; let curNumCodeBits = numBits + 1 ; let dict = Object . create ( null ) ; function resetAllParamsAndTablesToStartUpState ( ) { nextCode = endOfInfoCode + 1 ; curNumCodeBits = numBits + 1 ; dict = Object . create ( null ) ; } resetAllParamsAndTablesToStartUpState ( ) ; bb . push ( clearCode , curNumCodeBits ) ; let concatedCodesKey = "" ; for ( let i = 0 , len = actualCodes . length ; i < len ; ++ i ) { const code = actualCodes [ i ] ; const dictKey = String . fromCharCode ( code ) ; if ( ! ( dictKey in dict ) ) dict [ dictKey ] = code ; const oldKey = concatedCodesKey ; concatedCodesKey += dictKey ; if ( ! ( concatedCodesKey in dict ) ) { bb . push ( dict [ oldKey ] , curNumCodeBits ) ; if ( nextCode <= 0xfff ) { dict [ concatedCodesKey ] = nextCode ; if ( nextCode === 1 << curNumCodeBits ) curNumCodeBits ++ ; nextCode ++ ; } else { bb . push ( clearCode , curNumCodeBits ) ; resetAllParamsAndTablesToStartUpState ( ) ; dict [ dictKey ] = code ; } concatedCodesKey = dictKey ; } } bb . push ( dict [ concatedCodesKey ] , curNumCodeBits ) ; bb . push ( endOfInfoCode , curNumCodeBits ) ; return bb . flush ( ) ; } }