如何找到GIF图像中Image Block的起始位置?

How to find where does Image Block start in GIF images?

信息来源 - http://www.onicos.com/staff/iz/formats/gif.html#header

GIF 图像中,实际图像大小(宽度、高度)存储在图像块中。据我所知,Image Block 是 header 中包含的第一个块。 在实际块开始之前,有一个名为 Global Color Table(0..255 x 3 bytes)(from now-on 的内存分配GCT).如果我知道为 GCT 保留的字节数,我可以从 Image Block 中提取字节 5-9 并获得实际图像大小。

问题: 我怎样才能 know/learn GCT 的大小是多少?

GCT在哪里结束?

图像块从哪里开始?

图像块在哪里结束?

你需要的 gif enc/dec 你会在这里找到 3MF Project GIF

  1. GCT

    此块是可选的,并不总是出现在 GIF 文件中。大小由 GIF header 中的颜色数和位宽决定。我 decode/load 是这样的:

    struct _hdr
        {
        // Header
        BYTE Signature[3];      /* Header Signature (always "GIF") */
        BYTE Version[3];        /* GIF format version("87a" or "89a") */
        // Logical Screen Descriptor
        WORD xs;
        WORD ys;
        BYTE Packed;            /* Screen and Color Map Information */
        BYTE BackgroundColor;   /* Background Color Index */
        BYTE AspectRatio;       /* Pixel Aspect Ratio */
        } hdr;
    
    gcolor_bits= (hdr.Packed    &7)+1;  // global pallete
    scolor_bits=((hdr.Packed>>4)&7)+1;  // screen
    _gcolor_sorted =hdr.Packed&8;
    _gcolor_table  =hdr.Packed&128;
    scolors=1<<scolor_bits;
    gcolors=1<<gcolor_bits;
    
    • 如果_gcolor_table为真则GCT存在
    • GCT 大小按 3*gcolors [字节] 顺序存储 R,G,B
  2. 图像开始

    这个有点棘手,因为 GIF89a 文件可能包含许多可选块。你需要做一个解码循环检测块的类型,并根据它的目的 decoding/skipping 它。我这样做:

    struct _gfxext
        {
        BYTE Introducer;        /* Extension Introducer (always 21h) */
        BYTE Label;             /* Graphic Control Label (always F9h) */
        BYTE BlockSize;         /* Size of remaining fields (always 04h) */
        BYTE Packed;            /* Method of graphics disposal to use */
        WORD DelayTime;         /* Hundredths of seconds to wait    */
        BYTE ColorIndex;        /* Transparent Color Index */
        BYTE Terminator;        /* Block Terminator (always 0) */
        } gfx;
    
    struct _txtext
        {
        BYTE Introducer;        /* Extension Introducer (always 21h) */
        BYTE Label;             /* Extension Label (always 01h) */
        BYTE BlockSize;         /* Size of Extension Block (always 0Ch) */
        WORD TextGridLeft;      /* X position of text grid in pixels */
        WORD TextGridTop;       /* Y position of text grid in pixels */
        WORD TextGridWidth;     /* Width of the text grid in pixels */
        WORD TextGridHeight;    /* Height of the text grid in pixels */
        BYTE CellWidth;         /* Width of a grid cell in pixels */
        BYTE CellHeight;        /* Height of a grid cell in pixels */
        BYTE TextFgColorIndex;  /* Text foreground color index value */
        BYTE TextBgColorIndex;  /* Text background color index value */
    //      BYTE *PlainTextData;    /* The Plain Text data */
    //      BYTE Terminator;        /* Block Terminator (always 0) */
        };
    
    struct _remext
        {
        BYTE Introducer;        /* Extension Introducer (always 21h) */
        BYTE Label;             /* Comment Label (always FEh) */
    //      BYTE *CommentData;      /* Pointer to Comment Data sub-blocks */
    //      BYTE Terminator;        /* Block Terminator (always 0) */
        };
    
    struct _appext
        {
        BYTE Introducer;        /* Extension Introducer (always 21h) */
        BYTE Label;             /* Extension Label (always FFh) */
        BYTE BlockSize;         /* Size of Extension Block (always 0Bh) */
        CHAR Identifier[8];     /* Application Identifier */
        BYTE AuthentCode[3];    /* Application Authentication Code */
    //      BYTE *ApplicationData;  /* Point to Application Data sub-blocks */
    //      BYTE Terminator;        /* Block Terminator (always 0) */
        };
    // handle 89a extensions blocks
    _gfxext gfxext; gfxext.Introducer=0;
    _txtext txtext; txtext.Introducer=0;
    _remext remext; remext.Introducer=0;
    _appext appext; appext.Introducer=0;
    
    if((hdr.Version[0]=='8')
     &&(hdr.Version[1]=='9')
     &&(hdr.Version[2]=='a')) _89a=true; else _89a=false;
    
    if (_89a)
     for (;!f.eof;)
        {
        f.peek((BYTE*)&dw,2);
             if (dw==0xF921) { f.read((BYTE*)&gfxext,sizeof(_gfxext)); }
        else if (dw==0x0121) { f.read((BYTE*)&txtext,sizeof(_txtext)); for (;!f.eof;) { f.read(&db,1); if (!db) break; f.read(dat,DWORD(db)); } }
        else if (dw==0xFE21) { f.read((BYTE*)&remext,sizeof(_remext)); for (;!f.eof;) { f.read(&db,1); if (!db) break; f.read(dat,DWORD(db)); } }
        else if (dw==0xFF21) { f.read((BYTE*)&appext,sizeof(_appext)); for (;!f.eof;) { f.read(&db,1); if (!db) break; f.read(dat,DWORD(db)); } }
        else if ((dw&0x00FF)==0x0021) return; // corrupted file
        else break;                           // no extension found
        }
    
    • db是BYTE变量
    • dw是WORD变量
    • f 是我的文件缓存 class 成员不言自明我希望无论如何:
    • f.read(&data,size)size 字节读入 data
    • f.peek(&data,size) 做同样的事情但不更新文件中的位置
    • f.eof 表示已到达文件末尾

    必须在所有图像 header 开始后对每一帧执行此操作。

  3. 图像结束

    图像块以终止符结尾。所有图像块都以 BYTE 计数开头。如果是 zero 就是一个终结块。通常在图像之后很少有 BYTES 未被 LZW 数据使用,所以在填充整个图像区域后跳过所有块直到碰到零大小的块然后停止图像结尾。如果 BYTE 在这之后是 0x3B 十六进制你到达了 GIF 文件的末尾

[注释]

不要忘记用#pragma pack(1)#pragma pack()封装struct或者手动设置align1 BYTE。当心有符号数据类型的问题(LZW 数据是无符号的)所以在可以避免问题的地方改写或者只使用无符号变量(有足够的bit-width)来解码