One, foreword

Hello, everyone. I like file stream recently and am curious about it, such as PSDJS parsing, PSD file, XGPlayerJS video decoding, etc. The principle is similar, but these are too complicated to start. Finally find our hero of the day GIF.

What is GIF

Image Interchange Format (English: Graphics Interchange Format, or GIF for short) is a bitmap that recreates true-color images in 8-bit colors (i.e., 256 colors). It is actually a compressed document, encoded by LZW compression algorithm, which effectively reduces the transmission time of image files on the network. It is one of the most widely used image formats in the world Wide Web.

GIF file flow structure

1. It is composed of many different types of blocks

  • Unmarked blocks: file headers, logical screen descriptors, global color tables, local color tables
  • Control block: Graphical control extension
  • Graphics rendering blocks: plain text extensions, image descriptors
  • Special purpose blocks: application extensions, comment extensions, end of data stream tags
  • Image data block: Image data

2, GIF parsing principle

  • Analytical steps
  1. Js read.gifImage file (FETCH/Ajax /file)
  2. intoarrayBufferThe data flow
  3. willarrayBufferIn theDataView
  4. useDataViewThe underlying interfacegetInt8/getUit8readHexadecimal code
  5. willHexadecimal codeSolve for thetaUnicode encoded character,string,digital,Color value

export class Parser {
    private dataView: Uint8Array = new DataView(a);private index: number = 0;
    constructor(url: string) {
       fetch(url)
            .then(response= > response.arrayBuffer())
            .then(arrayBuffer= > new DataView(arrayBuffer))
            .then(dataView= > {
                this.dataView = dataView;
                const str_1 = this.readUint8();
                / / 71
                const str_2 = this.readUint8();
                / / 73
                const str_3 = this.readUint8();
                / / 70
                const str = String.fromCharCode(str_1, str_2, str_3);
                // GIF
            });
    }
    // Read an unsigned 8-bit integer (unsigned bytes)
    readUint8(): number {
        const value = this.dataView.getUint8(this.index);
        this.seek(1);
        return value;
    }
    // Find the index location
    seek(x: number) :void {
        this.index += x; }}new Parser('/path/xx.gif');
Copy the code
  • Decoding process:
  1. Along theHeader,LogicalScreenDescrptor,GlobalColorTable(not necessary),GraphicControlExtensionApplication(not necessary),CommentExtension(not necessary),ImageDescriptor,LocalColorTable(not necessary),ImageData,PlainTextExtensionFinish the first frame (picture)
  2. And then repeatGraphicControlExtensionImageDescriptor,ImageDataRead the remaining frames of picture data
  3. Until it finally readsTrailerGIF data stream end tag is completed
  4. Of course, during the reading process, each block has its own special coding mark, which is described in detail in the following code implementation

GIF file flow data block

1. -header

  1. Description: Identifies in the context of a GIF file header data stream, this signature field marks the beginning of the data stream, and the version number field identifies the set of capabilities fully required by the decoder to process the data stream. This block is required; There is only one Header per data stream.
  2. Version required: Not applicable. This block is not limited by the version number. This block must appear at the beginning of each data flow.
  3. Syntax:

  1. View the following information in the GIF data stream: signature and version

Open the.gif image file with the Hex Fiend editor

As seen in the editor above:

  • On the left side of the selected47 49 46 38 39 61, byte encoding
  • On the right side of the selectedG I F 8 9 a, Unicode encoded characters

One-to-one correspondence, decoding principle, as shown in the figure below:

ArrayBuffter (String, String, String, String); arrayBuffter (String, String, String)

  1. Code implementation: is it everyone’s favorite or look at the code
// File header - part of the code
export class Header {
    private signature: string  = ' ';
    private version: string  = ' ';
    constructor(private stream: Stream) {
        this.stream = stream;
    }

    parse() {
        // Sign, "GIF"
        this.signature = this.stream.readString(3); 
        // version, "89A"
        this.version = this.stream.readString(3);
        if (this.signature ! = ='GIF') throw new Error('Not a GIF file.'); }}Copy the code

The Stream class is based on the low-level interface of DataView implementation, detailed Header class implementation before Github view.

2. LogicalScreenDescriptor – LogicalScreenDescriptor

  1. Description: The logical screen descriptor contains the parameters required to define the display device area in which the image will be rendered. The coordinates in this block are given relative to the upper left corner of the virtual screen; This block is required; A logical screen descriptor must exist for each data flow.
  2. The block is version-independent: the block must come immediately after the Header.
  3. Syntax:

  • Logical Screen Width:Logical screen width(in pixels), occupying 2 bytes of space
  • Logical Screen Height:Height of logical screen(in pixels), occupying 2 bytes of space
  • Packed Fields: compressed field, takes up 1 byte space and contains 4 values
    1. Global Color Table Flag:Global color table flagFlags indicating the existence of a global color table; If flags are set, the global color table immediately follows the logical screen descriptor. The logo also selects the interpretation of the background color index; If this flag is set, the value of the background color index field is used as the table index for the background color. (This field is the most significant bit of a byte.) 0 – There is no global color table and the background color index field makes no sense. 1 – A global color table will follow, and the background color index field is meaningful.
    2. Color Resolution:Color resolutionThe number of digits of the color is reduced by 1. The color has 1, 8, 16, 32, and so on. By definition, GIF images cannot be more than 256 colors or 8 bits deep.
    3. Sort Flag:Sorting marks 0– Not set.1– In descending order of importance, the most important color comes first.
    4. Size of Global Color Table: Size of the global color tableIf the global color table flag is set to 1, the value in this field is used to count the number of bytes contained in the global color table. To determine the actual size of the color table, increase 2 to [field value + 1]. Even if the global color table is not specified, this field is set according to the above formula so that the decoder can choose the best graphics mode to display the stream. (This field consists of the three least significant bits of a byte.)
  • Background Color Index: Background color indexBackground color Index of the global color table. The background color is the color applied to pixels on the screen that are not covered by the image. If the global color table flag is set to (zero), this field should be zero and should be ignored.
  • Pixel Aspect Ratio: Pixel aspect ratioA factor used to calculate an approximation of the aspect ratio of pixels in the original image. If the field does not have a value of 0, the approximate aspect ratio will be calculated according to the formula: aspect ratio = (pixel aspect ratio + 15) / 64, where the pixel aspect ratio is defined as the quotient of the pixel width to its height. The range of values in this field allows the widest pixel of 4:1 to the highest pixel of 1:4 to be specified in 1/64 increments. 0 – Gives no aspect ratio information. 1 to 255 – The value used in the calculation.
  1. Code implementation: is it everyone’s favorite or look at the code
// Logical screen descriptor - part of the code
export class LogicalScreenDescriptor {
    private width: number = 0;
    private height: number = 0;
    // Fill in the fields
    private packedFields: Array<number> = [];
    private globalColorTableFlag: number = 0;
    private colorResolution: number = 0;
    private sortFlag: number = 0;
    private globalColorTableSize: number = 0;
    private backgroundColorIndex: number = 0;
    private pixelAspectRatio: number = 0;
    constructor(private stream: Stream) {
        this.stream = stream;
    }

    parse() {
        this.width = this.stream.readUint16();
        this.height = this.stream.readUint16();
        this.packedFields = byteToBits(this.stream.readUint8());
         const bits: Array<number> = [...this.packedFields];
        // 1 - Global color table flag
        this.globalColorTableFlag = bits.shift() || 0;
        // 3 - Color resolution
        this.colorResolution = bitsToNumber(bits.splice(0.3)) + 1;
        // 5 - Sort flags
        this.sortFlag = bits.shift() || 0;
        // 7 - Size of global color table
        this.globalColorTableSize = bitsToNumber(bits.splice(0.3));
        this.handlerPackedField(this.packedFields);
        this.backgroundColorIndex = this.stream.readUint8();
        this.pixelAspectRatio = this.stream.readInt8();
    }

    // Use the following command
    hasGlobalColorTable(): boolean {
        return!!!!!this.globalColorTableFlag;
    }

    // Use the following command
    getGlobalColorTableSize() {
        return this.globalColorTableSize; }}Copy the code

Click on Github to see the LogicalScreenDescriptor implementation details.

3, global color chart/local color table – GlobalColorTable/LocalColorTable

  1. Description: This block contains a color table that is a sequence of bytes representing red-green-blue triples. Image and plain text extensions without a local color table use a global color table. Its presence is signaled by the global color table flag set to 1 in the logical screen descriptor; If present, it follows the logical screen descriptor and contains a number of bytes equal to 3 x 2^ (global color table size +1). This block is optional; There can be at most one global color table per data stream.
  2. Version required: 87A
  3. Syntax:

  1. Extension and scope: The scope of this block is the entire data stream. This block cannot be modified by any extension.
  2. Code implementation: get global color table size s, S + 1, 1 << s + 1
export class ColorTable {
    private colors: ColorTableData  = [];
    constructor(private stream: Stream) {
        this.stream = stream;
    }

    parse({ size = 0 }: ParseParam) {
        // The number of bytes equal to 3 x 2^ (global color table size +1).
        const colorTableSize = 1 << size + 1;
        for (let i = 0; i < colorTableSize; i++) {
            this.colors.push(this.stream.readUint8Array(3)); }}}Copy the code

4. Graph control extension -GraphicControlextension

  1. Description: The graphics control extension contains parameters to use when working with graphics rendering blocks. The scope of this extension is the first graphic rendering block to follow. The extension contains only one data subblock. This block is optional; Up to one graphics control extension can precede a graphics rendering block. This is the only limit on the number of graphical control extensions that may be included in the data flow.
  2. Version required: 89A.
  3. Syntax:

  • Extension Introducer: Extended import character, marking the beginning of the extension block. This field contains fixed values0x21.
  • Graphic Control Label:Graphic control labelIdentifies the current block as a graphical control extension. This field contains fixed values0xF9.
  • Block Size:The block size, the number of bytes in the block, after the block size field, up to but not including the block terminator. This field contains fixed values4.
  • Disposal Method:Processing methodRepresents the processing mode of the graph after display.
    • 0Unspecified disposal, the decoder does not need to take any action.
    • 1Don't deal with, the graphics will be left in place.
    • 2Restore to background colorThe area used by the graph must be restored to the background color.
    • 3Revert back to beforeThe decoder needs to restore the area covered by the graph with the content before rendering the graph.
    • 4 to 7To be defined.
  • User Input Flag:User input flag– Indicates whether user input is required before continuing. If this flag is set, processing continues as user input is entered. The nature of user input is determined by the application (carriage return, mouse button click, and so on).
    • 0 – No user input is required.
    • 1 – Requires user input.

When a delay time is used and a user input flag is set, processing continues when user input is received or when the delay time expires, whichever comes first.

  • Transparency Flag:The transparency flag— Indicates whether a transparent index is given in a transparent index field. (This field is the least significant bit of a byte.)
    • 0 – No transparent index is given.
    • 1 – Gives a transparent index.
  • Delay Time:Delay timeIf not 0, this field specifies the hundredth of the time to wait before continuing processing the data stream(1/100)Seconds. Immediately after rendering the graphics, the clock starts ticking. This field can be used in conjunction with the user input flag field.
  • Transparency Index:Transparency indexThe transparency index is such that, when encountered, the corresponding pixel of the display device is not modified and processing continues to the next pixel. The index exists if and only if Transparency Flag is set to 1.
  • Block Terminator:Block terminatorThis zero-length data block marks the end of graphical control extensions.
  1. Extension and scope: The scope of this block is the entire data stream. This block cannot be modified by any extension.
  2. Code implementation:
export class GraphicsControlExtension extends BaseExtension {
    private introducer: string = ' ';
    private label: string = ' ';
    private blockSize: number = 0;
    private packedFields: Array<number> = [];
    private reserved: Array<number> = [];
    private disposalMethod: number = 0;
    private userInputFlag: number = 0;
    private transparentColorFlag: number = 0;
    private delayTime: number = 0;
    private transparentColorIndex: number = 0;
    private blockTerminator: string = ' ';
    
    constructor(private stream: Stream) {
        this.stream = stream;
    }

    parse({ introducer = 0, label = 0 }: ParseParam): void {
        this.introducer = String.fromCharCode(introducer);
        this.label = numberToHex(label);

        this.blockSize = this.stream.readUint8(); 
        this.packedFields = byteToBits(this.stream.readUint8());
        const bits = [...this.packedFields];
        this.reserved = bits.splice(0.3); / / the reserved
        this.disposalMethod = bitsToNumber(bits.splice(0.3));
        this.userInputFlag = bits.shift() || 0;
        this.transparentColorFlag = bits.shift() || 0;
        this.delayTime = this.stream.readUint16();
        this.transparentColorIndex = this.stream.readUint8();
        
        this.blockTerminator = this.stream.readString(1); }}Copy the code

Click on Github to see the GraphicsControlExtension class implementation details.

  1. Advice:
  • User Input Flag:Processing method, reverting to the previous pattern intended for small parts of the graph; The use of this mode puts forward strict requirements for the decoder to store the parts of the graph that need to be saved. Therefore, this pattern should be used with caution. This mode is not intended to save the entire graph or large areas of the graph; In such cases, the encoder should make every effort to make the part of the graph to be recovered a separate graph in the data stream. If the decoder is unable to save a graphic area marked “Revert to Previous”, it is recommended that the decoder revert to the background color.
  • Disposal Method:User input flagWhen the flag is set, indicating that user input is required, the decoder can ring a bell (0x07) to remind the user that input is waiting. When no delay time is specified, the decoder should wait indefinitely for user input. Encoders are advised not to set user input flags without specifying a delay time.

5, Application Extension – Application Extension

  1. Description: Application extensions contain application-specific information; It follows the extension block syntax, as described below, and has a block label of 0xFF.
  2. Version required: 89A.
  3. Syntax:

  • Extension Introducer:Extended import characterTo define this block as an extension. This field contains fixed values0x21.
  • Application Extension Label:Apply extension labels— Identify the block as an application extension. This field contains fixed values0xFF.
  • Block Size:The block size, the number of bytes in this extension block, following the block size field up to, but not including, the beginning of the application data. This field contains fixed values11.
  • Application Identifier:Application identifier, a sequence of eight printable ASCII characters used to identify applications that have application extensions.
  • Application Authentication Code:Application verification code— a three-byte sequence used to validate the application identifier. An application can use an algorithm to evaluate binary code that uniquely identifies it as an application that has an application extension.
  1. Extension and scope: This block has no scope. This block cannot be modified by any extension.
  2. Code implementation:

export class ApplicationExtension {
    private introducer: string = ' ';
    private label: string = ' ';
    private blockSize: number = 0;
    private identifier: string = ' ';
    private authenticationCode: string = ' ';
    private appData: AppData;

    private netscapeExtension: NetscapeExtension;
    private xmpdataExtension: XMPDataExtension;
    private unknownApplicationExtension: UnknownApplicationExtension;

    constructor(private stream: Stream) {
        super(stream);
        this.netscapeExtension = new NetscapeExtension(stream);
        this.xmpdataExtension = new XMPDataExtension(stream);
        this.unknownApplicationExtension = new UnknownApplicationExtension(stream);
        this.appData = {} as AppData;
    }

    parse({ introducer = 0, label = 0 }: ParseParam): void {
        this.introducer = String.fromCharCode(introducer);
        this.label = numberToHex(label);
        this.blockSize = this.stream.readInt8(); // Always 11
        this.identifier = this.stream.readString(8);
        this.authenticationCode = this.stream.readString(3);
        if (this.identifier === 'NETSCAPE') {
            this.netscapeExtension.parse({ introducer, label });
            this.appData = this.netscapeExtension.export();
        } else if (this.identifier === 'XMP Data') {
            this.xmpdataExtension.parse({ introducer, label });
            this.appData = this.xmpdataExtension.export();
        } else {
            this.unknownApplicationExtension.parse({ introducer, label });
            this.appData = this.unknownApplicationExtension.export(); }}}Copy the code

Click on github to see the ApplicationExtension class implementation details.

6. Image Descriptor – Image Descriptor

  1. describeEach image in the data flow consists of an image descriptor, an optional local color table, and image data. Each image must fit within the boundaries of the logical screen, as defined in the logical screen descriptor. Image descriptors contain the parameters needed to process tab-based images. The coordinates given in this block are the coordinates within the logical screen, in pixels. The Block is a graphic-rendering Block that optionally has one or more control blocks in front of it, for exampleGraphic Control ExtensionAnd you can choose to follow oneLocal Color Table; The image descriptor always follows the image data. The image requires this block. Each image in the data flow must have an image descriptor. An unlimited number of images can exist per data stream.
  2. Version required: 87A.
  3. Syntax:

  • Image Separator:Image separator– Identifies the beginning of the image descriptor. This field contains fixed values0x2C.
  • Image Left Position:Image left position– The column number of the left edge of the image relative to the left edge of the logical screen, in pixels. The leftmost column of the logical screen is0.
  • Image Top Position:Image top position, the number of lines (in pixels) of the top edge of the image relative to the top edge of the logical screen. The top line of the logical screen is0.
  • Image Width:The width of the image– In pixels.
  • Image Height:Height of the image– In pixels.
  • Local Color Table Flag:Local color table flags— indicates the presence of a local color table immediately following the image descriptor. (This field is the most significant bit of a byte.)
    • 0 – The local color table does not exist. Use a global color table (if available).
    • 1 – The local color table exists and follows immediately after this image descriptor.
  • Interlace Flag:Interlaced logo– Indicates whether the image is interlaced. The images are interleaved in four staggered modes.
    • 0 – Images are not interlaced.
    • 1 – Image interleaving.
  • Sort Flag:Sorting marks– Indicates whether the local color table is sorted. If this flag is set, the local color table is sorted in descending importance. Usually, the order is of decreasing frequency, with the most common colors coming first. This helps decoders with fewer available colors select the best subset of colors; The decoder can use the initial segment of the table to render the graph.
    • 0 – Not ordered.
    • 1 – In descending order of importance, the most important color comes first.
  • Size of Local Color Table:Size of the local color table– If the local color table flag is set to1, the value in this field is used to calculate the number of bytes contained in the local color table. To determine the actual size of the color table, select2Increase to the value of the field+ 1. If no local color table is specified, the value should be0. (The field is composed of bytesthreeLeast significant bits.)
  1. Extension and scope: The scope of this block is the table-based image data block that follows it. This block can be graphically modified
  2. Code implementation:
export class ImageDescriptor {
    private introducer: string = ' ';
    private left: number = 0;
    private top: number = 0;
    private width: number = 0;
    private height: number = 0;
    
    private packedFields: Array<number> = [];
    private localColorTableFlag: number = 0;
    private interlaceFlag: number = 0;
    private sortFlag: number = 0;
    private reserved: Array<number> = [];
    private localColorTableSize: number = 0;

    constructor(private stream: Stream) {
        this.stream = stream;
    }

    hasLocalColorTable(): boolean {
        return!!!!!this.localColorTableFlag;
    }

    getLocalColorTableSize(): number {
        return this.localColorTableSize;
    }

    parse({ introducer = 0 }: ParseParam): void {
        this.introducer = String.fromCharCode(introducer);
        
        this.left = this.stream.readUint16();
        this.top = this.stream.readUint16();
        this.width = this.stream.readUint16();
        this.height = this.stream.readUint16();

        this.packedFields = byteToBits(this.stream.readUint8());
        const bits = [...this.packedFields];
        this.localColorTableFlag = bits.shift() || 0;
        this.interlaceFlag = bits.shift() || 0;
        this.sortFlag = bits.shift() || 0;
        this.reserved = bits.splice(0.2);
        this.localColorTableSize = bitsToNumber(bits.splice(0.3)); }}Copy the code

Click on Github to see the ImageDescriptor class implementation details.

7, Table Based Image Data

  1. describe: The image data for a table-based image consists of a series of sub-blocks, each of which is up to 255 bytes in size and contains an index to the active color table for each pixel in the image. The pixel index is arranged from left to right and top to bottom. Each index must be within the size range of the active color table, starting at 0. Index sequence using a variable length codeLZWThe algorithm is encoded as described in Appendix F
  2. Version required: 87A.
  3. Syntax: The image data format is as follows

  • LZW Minimum Code Size:LZW Minimum code size– This byte is determined to be used in image dataLZWThe initial bits of code, as described in Appendix F.
  • Image Data: Picture data – look belowData Sub-blocksThe rules.
  1. Code implementation:
export class ImageData {
    private lzwMinimumCodeSize: number = 0;
    private blocks: blocks: ArrayThe < {offset: number.length: number} > = [];constructor(private stream: Stream, private subBlocks: SubBlocks) {
        this.stream = stream;
        this.subBlocks = new subBlocks(stream);
    }

    parse() {
        this.lzwMinimumCodeSize = this.stream.readUint8();
        // look -> 8, Data sub-blocks, LZW Data
        this.subBlocks.parse()
        this.blocks = this.subBlocks.readSubBlocks(); }}Copy the code

Click on Github to see the ImageData class implementation details.

  1. Extension and scope: This block has no scope and contains raster data. Extensions used to modify table-based images must appear before the corresponding image descriptor.
  2. As described in Appendix F: this isThe official documentationintroduceLZWRules, more complicated, next time to introduce separately.

8, Data sub-blocks – Data sub-blocks

  1. Description: A data subblock is a unit that contains data. They have no labels, and these blocks are processed in the context of the control block, regardless of where the data block is specified in the format. The first byte of a data subblock indicates the number of bytes of data to follow. A data subblock can contain anywhere from 0 to 255 data bytes. The size of the block does not take into account the size byte itself, so the empty subblock is a subblock of the size field containing 0x00.
  2. Version required: 87A.
  3. Syntax:

  • Block Size:The block size– Number of bytes in a data subblock; The size must be between 0 and 255 bytes, inclusive.
  • Data Values:The data values– Any 8-bit value. The number of data values must be exactly the same as the number specified by the block size field.
  1. Code implementation:
export class SubBlocks {
    private blocks: blocks: ArrayThe < {offset: number.length: number} > = [];private length: number = 0;constructor(private stream: Stream) {
        this.stream = stream;
    }

    parse() {
        const offset = this.stream.getOffset();
        let blockSize = this.stream.readUint8();
        let length = 1;
        const blocks: ArrayThe < {offset: number.length: number} > = [];while(blockSize > 0) {
            blocks.push({ offset: offset + length, length: blockSize });
            length += blockSize + 1;
            this.stream.seek(blockSize);
            blockSize = this.stream.readUint8();
        }
        length += 1;
        
        this.blocks = blocks;
        this.length = length;
    }
    
    readSubBlocks() {
        return this.blocks; }}Copy the code
  1. Extension and scope: This type of block always appears as part of a larger unit. It has no scope of its own.

Five, giF-Parser key logic implementation

  1. Key logic flow chart

2. Code implementation

export class Parser {
    private parsed: boolean;
    private stream: Stream;
    private header: Header;
    private logicalScreenDescriptor: LogicalScreenDescriptor;
    private globalColorTable: GlobalColorTable;
    private graphicsControlExtension: GraphicsControlExtension;
    private plainTextExtension: PlainTextExtension;
    private commentExtension: CommentExtension;
    private applicationExtension: ApplicationExtension;
    private unknownExtension: UnknownExtension;
    private imageDescriptor: ImageDescriptor;
    private imageData: ImageData;

    constructor(arrayBuffer: ArrayBuffer) {
        console.log('GifParser init')
        this.parsed = false;
        this.stream = new Stream(arrayBuffer, true);
        this.header = new Header(this.stream);
        this.logicalScreenDescriptor = new LogicalScreenDescriptor(this.stream);
        this.globalColorTable = new GlobalColorTable(this.stream);
        this.graphicsControlExtension = new GraphicsControlExtension(stream);
        this.commentExtension = new CommentExtension(stream);
        this.plainTextExtension = new PlainTextExtension(stream);
        this.applicationExtension = new ApplicationExtension(stream);
        this.unknownExtension = new UnknownExtension(stream);
        this.imageDescriptor = new ImageDescriptor(stream);
        this.imageData = new ImageData(stream);
        this.parse();
    }

    private parse(): void {
        if (this.parsed) {
            return;
        }
        // Parse the file header
        this.header.parse();
        // Parse the logical screen descriptor
        this.logicalScreenDescriptor.parse();
        if (this.logicalScreenDescriptor.hasGlobalColorTable()) {
            const size = this.logicalScreenDescriptor.getGlobalColorTableSize();
            // Parse the global color table
            this.globalColorTable.parse({ size });
        }
       
        // Is there any data left
        while (this.stream.hasMore()) {
            // Import character, parse start symbol, "!" , ", ", ";"
            const introducer = this.stream.readUint8();
            // If "!" , corresponding to the extension
            if(String.fromCharCode(introducer) === '! ') {
                // Extension type, label: 0xF9, 0xFE, 0x01, 0xFF
                const label = this.stream.readUint8();
                // 0xF9: Parse Graph Control Extension
                if (label === 0xF9)  this.graphicsControlExtension.parse({ introducer, label });
                // 0xFE: parse the comment extension
                else if (label === 0xFE) this.commentExtension.parse({ introducer, label });
                // 0x01: Parse plain text extensions
                else if (label === 0x01) this.plainTextExtension.parse({ introducer, label });
                // 0xFF: Parse the application extension
                else if (label === 0xFF) this.applicationExtension.parse({ introducer, label });
                // Parse no other do not know the extension
                else this.unknownExtension.parse({ introducer, label });
             }
             // If it is ", ", the corresponding picture
             else if (String.fromCharCode(introducer) === ', ') {
                 // Parse the image descriptor
                 this.imageDescriptor.parse({ introducer });
                 if (this.imageDescriptor.hasLocalColorTable()) {
                      const size = this.imageDescriptor.getLocalColorTableSize();
                      // Parse the local color table
                      this.localColorTable.parse({ size });
                      // Parse the image data
                      this.imageData.parse(); }}// If ";" , the corresponding end
             else if (String.fromCharCode(introducer) === '; ') {
                  console.log('end block');
             }
             // Fill this with 0
             else {
                 throw new Error('Unknown block: 0x' + introducer.toString(16))}}this.parsed = true; }}Copy the code

Click on Github to see the Parser implementation details.

  1. Decoder effect

  1. Gif-parser is available for installation on NPM
  • The installation
npm i @n.see/gif-parser --save
Copy the code
  • use
import { onMounted } from 'vue';
import { Parser } from '@n.see/gif-parser'; 

onMounted(() = > { 
    fetch('/src/assets/03.gif')
    .then((resp) = > resp.arrayBuffer())
    .then(arrayBuffer= > {
        // Parse the GIF file stream
        const parser = new Parser(arrayBuffer);
        / / GIF height to width
        const [width, height] = parser.getSize();
        // Export data
        parser.export();
        // Get data
        const dataList = parser.getDataList();
    });
});
Copy the code
  1. github
  • Demo: GIF
  • Parser: Git-parser
  • Gif-player (native JS)
  • Gif-player-vue-next (vue-next component)

Vi. Reference materials

  • W3C GIF89a specification
  • What’s In A GIF
  • Bit depth and color depth