Gif 文件规范 - 框架的注释属性

问题描述 投票:0回答:3

我期待着一种在 gif 文件的每一帧中存储

text
的方法。不打印图像中的文本,而是作为属性添加。微软制作的一个旧程序可以为每个框架设置文本。

Comments

如您所见,每帧都有一个“注释”字段。

现在,我的问题是:

这个字段是 GIF 规范认可的吗?几乎没有任何文件可以这么说。 (其实是有的)

如果是:

位于哪里?使用这些方法之一?

     protected void WriteGraphicCtrlExt()
     {
    fs.WriteByte(0x21); // extension introducer
    fs.WriteByte(0xf9); // GCE label
        fs.WriteByte(4); // data block size

        int transp, disp;

    if (transparent == Color.Empty) 
    {
    transp = 0;
    disp = 0; // dispose = no action
    } 
    else 
    {
    transp = 1;
    disp = 2; // force clear if using transparent color
        }

        //If first frame, no transparency and no dispose.
        if (firstFrame)
        {
            disp = 0;
            transp = 0;
        }
        else
        {
            if (dispose >= 0)
            {
                disp = dispose & 7; // user override
            }
            disp <<= 2;
        }

        // packed fields
        fs.WriteByte( Convert.ToByte( 0 | // 1:3 reserved
            disp | // 4:6 disposal
            0 | // 7   user input - 0 = none
            transp )); // 8   transparency flag

        WriteShort(delay); // delay x 1/100 sec
        fs.WriteByte( Convert.ToByte( transIndex)); // transparent color index
        fs.WriteByte(0); // block terminator
    }


    protected void WriteImageDesc()
    {

        fs.WriteByte(0x2c); // image separator
        WriteShort(0); // image position x,y = 0,0
        WriteShort(0);
        WriteShort(width); // image size
        WriteShort(height);
        // packed fields
        if (firstFrame) 
        {
            // no LCT  - GCT is used for first (or only) frame
            fs.WriteByte(0);
        } 
        else 
        {
            // specify normal LCT
            fs.WriteByte( Convert.ToByte( 0x80 | // 1 local color table  1=yes
                0 | // 2 interlace - 0=no
                0 | // 3 sorted - 0=no
                0 | // 4-5 reserved
                palSize ) ); // 6-8 size of color table
        }
    }

编辑:

我找到了一种方法来做到这一点,就像 Hans Passand 所写的那样:

protected void WriteComment(string comment)
{
        fs.WriteByte(0x21);
        fs.WriteByte(0xfe);

        byte[] lenght = StringToByteArray(comment.Length.ToString("X"));

        foreach (byte b in lenght)
        {
            fs.WriteByte(b);
        }

        WriteString(comment);
}
c# comments gif specifications
3个回答
3
投票

最好只使用该工具(Microsoft 的 GIFAnimator,可通过 MSDN 订阅获得)并使用十六进制查看器查看它生成的内容。将GIF 规范放在手边,以便您可以将您看到的内容与规范相关联。

我创建了一个非常简单的 GIF 文件,其中包含两个 8x8 帧,为第一个帧键入“frame 1”,为第二个帧键入“number 2”。产生了这个十六进制转储:

enter image description here

我用红色突出显示了相关块。它们符合规范中的第 24 节“注释扩展”:

评论扩展包含文本信息, 不是 GIF 数据流中实际图形的一部分。适合 包括有关图形、学分、描述或任何内容的评论 其他类型的非控制和非图形数据。评论扩展 可能被解码器忽略,或者可能被保存以供以后处理; 在任何情况下,评论扩展都不应扰乱或干扰 随着数据流的处理。

该块是可选的;任意数量的它们都可能出现在数据流中。

注意它们后面是如何跟随一个 21 F9 块,一个“图形控制扩展”块,规范中的第 23 节。它描述了图像文件中的每一帧,“延迟时间”值至关重要。接下来是 2C,一个“图像描述符”块,规范中的第 20 节。它包含每一帧的图像数据。

回答您的具体问题:

这个字段是 GIF 规范认可的吗?

是的,明确如第 24 节所述。应用程序完全取决于应用程序是否合适地使用它们。它们只是注释性的,对于其他 GIF 实用程序或图像使用者来说没有任何特殊意义。

位于哪里?使用这些方法之一?

不,该代码不写注释,它只发出 21 个 F9 和 2C 块。只需更改代码以在其前面插入 21 FE 块即可。像这样的东西:

protected void WriteGraphicCtrlExt(string comment)
{
    if (!string.IsNullOrEmpty(comment)) {
       fs.WriteByte(0x21);
       fs.WriteByte(0xfe);
       var bytes = Encoding.ASCII.GetBytes(comment);
       fs.WriteByte((byte)bytes.Length);
       fs.Write(bytes, 0, bytes.Length);
       fs.WriteByte(0);
    }
    // Rest of code
    //...
}

2
投票

嗯,有 GifLib 项目可以帮助你解决这个问题。具体来说,这些文件:

http://giflib.codeplex.com/SourceControl/latest#GifEncoder.cs,第 #87 行 Encode(),它在代码中指定以什么顺序将内容写入输出 gif 流

对于评论扩展,有一些提示:

http://giflib.codeplex.com/SourceControl/latest#CommentEx.cs,第 57 行 GetBuffer():

        internal byte[] GetBuffer()
        {            
            List<byte> list = new List<byte>();
            list.Add(GifExtensions.ExtensionIntroducer); // 0x21
            list.Add(GifExtensions.CommentLabel); // 0xFE
            foreach (string coment in CommentDatas)
            {
                char[] commentCharArray = coment.ToCharArray();
                list.Add((byte)commentCharArray.Length);
                foreach (char c in commentCharArray)
                {
                    list.Add((byte)c);
                }
            }
            list.Add(GifExtensions.Terminator); // 0
            return list.ToArray();
        }

0
投票

处理 GIF 文件有时有点棘手: https://giflib.sourceforge.net/whatsinagif/bits_and_bytes.html

令人遗憾的是,使用 GifBitmapEncoder 或 GifBitmapDecoder 无法开箱即用地操作元数据上的 GIF 注释,请参阅示例:https://github.com/heinrichelsigan/area23.at/blob/main/www/Area23 .At.Www.S/Util/GifMetadataAdapter.cs

/// <summary>
/// GenerateGifWithComment - hackish raw method to add a GIF-Comment
/// </summary>
/// <param name="codecImage">existing Bitmap to modify</param>
/// <param name="gifComment">comment, that you want to insert in CompuServe GIF</param>
/// <param name="saveFilePath">filepath to save modified GIF file with comment on filesysten with full directory path</param>
/// <returns></returns>
public Bitmap GenerateGifWithComment(Bitmap codecImage, string gifComment = null, string saveFilePath = null)
{
    string outFullPath = System.AppDomain.CurrentDomain.BaseDirectory + System.IO.Path.PathSeparator;
    string outGenerated = outFullPath + DateTime.UtcNow.ToString("{yyyy-MM-dd_HH.mm.ss.fff}.gif");
        
    saveFilePath = saveFilePath ?? outGenerated;
    gifComment = gifComment ?? "";
    gifComment = gifComment.Trim("\r\b\a\v".ToCharArray())            ;            
        
    MemoryStream gifMemStream = new MemoryStream();     // create gifMemStream - a new MemoryStream()
    codecImage.Save(gifMemStream, ImageFormat.Gif);     // save codecImage to gifMemStream in ImageFormat.Gif
    byte[] imgGifBytes = gifMemStream.ToArray();        // get all byte[] imgGifBytes from gifMemStream
        
    List<byte> toByteList = new List<byte>();
    bool flagOnce = false;

    for (int bydx = 0; bydx < imgGifBytes.Length; bydx++)
    {
        // this kind of MIT Magick Cookie will detect position, where to insert before GIF-Comment
        //  insert GIF-Comment before <=  0x21 0xF9 0x04 0x01 0x00
        //  insert GIF-Comment before <=  0x21 0xFE 0x04 0x01 0x00
        if ((imgGifBytes[bydx] == (byte)0x21) &&        // 0x21 (byte)((char)'!')
            (imgGifBytes[bydx + 1] == (byte)0xf9 ||     // 0xF9 | 0xFE
                imgGifBytes[bydx + 1] == (byte)0xfe) &&  
            (imgGifBytes[bydx + 2] == (byte)0x04) &&    // 0x04     EOT
            (imgGifBytes[bydx + 3] == (byte)0x01) &&    // 0x01     SOH                        
            (imgGifBytes[bydx + 4] == (byte)0x00))      // 0x00     NUL
        {
            if (!flagOnce)
            {
                foreach (byte b in GifCommentBytes(gifComment))
                {
                    toByteList.Add(b);
                }
                flagOnce = true;
            }
        }

        if ((bydx == imgGifBytes.Length - 1) &&         // only short check at end of gifBytes, 
            ((imgGifBytes[bydx - 1] == (byte)0x0) &&    // that penultimate byte is NUL 
                imgGifBytes[bydx] == (byte)0x3b))       // and last byte of GIF is 0x3b  
        { ; } /* <= only breakpoint trigger */

        toByteList.Add(imgGifBytes[bydx]);               // Add next byte from gifBytes to ToByteList
    }

    // save original codecImage as Gif to filename $"{dateTime.UtcNow:yyyy-MM-dd_HH.mm.ss.fff}_original.gif"
    outGenerated = outFullPath + DateTime.UtcNow.ToString("{yyyy-MM-dd_HH.mm.ss.fff}_original.gif");            
    codecImage.Save(outGenerated, ImageFormat.Gif);

    // open a FileStream and write (byte[])toByteList.ToArray() fully out => flush
    using (Stream fs = File.Open(saveFilePath, FileMode.Create, FileAccess.ReadWrite))
    {
        fs.Write(toByteList.ToArray(), 0, toByteList.Count);    // writes toByteList.Count byte[] from toByteList
        fs.Flush();                                             
    }

    Bitmap gifMap = new Bitmap(saveFilePath);
    return gifMap;
}


/// <summary>
/// GifCommentBytes - gets a byte[] for further comment to add to GIF
/// </summary>
/// <param name="c">GIF-Comment as <see cref="string"/></param>
/// <returns><see cref="byte[]">array of byte</see></returns>
protected virtual byte[] GifCommentBytes(string c)
{
    if (string.IsNullOrEmpty(c))
        return new byte[0];
                    
    List<byte> byteList = new List<byte>();     // create a new generic List<byte>()                   
    byteList.Add((byte)0x21);                   // Write first 0x21 '!' detection sequence
    byteList.Add((byte)0xfe);                   // Write 0xfe ((byte)254) as snd byte

    byte[] bytes = Encoding.ASCII.GetBytes(c);  // get byte[] from string ASCII
    byte b0 = Convert.ToByte(c.Length & 0xff);  // write first content length of now following comment
    if (c.Length > (int)0xff)                   // TODO: might be still buggy, if comment length >= 256 ;(
    {                
        byte b1 = Convert.ToByte((c.Length >> 8) & 0xff);
        byteList.Add(b1);                       // add most significant byte  from content length to List<byte>
    }
    byteList.Add(b0);                           // finally add the least significant byte from content length to List<byte>

    foreach (byte b in bytes)                   // loop through all comment bytes
    {
        byteList.Add(b);                        // add byte per byte from byte[] comment to List<byte>
    }

    byteList.Add((byte)0x0);                    // add 0x00 as termination symbol to finish comment header in GIF

    return byteList.ToArray();
}
© www.soinside.com 2019 - 2024. All rights reserved.