【问题标题】:How to create a gif image with C#如何使用 C# 创建 gif 图像
【发布时间】:2013-07-22 06:26:21
【问题描述】:

我想创建一个包含多个帧的 gif 文件。 我想用微软支持的方法--Image.SaveAdd

但是我不知道如何设置EncoderParameters参数来组成一个gif文件。

我找不到要参考的文件。那么如何用Image.SaveAdd创建一个gif文件

【问题讨论】:

    标签: c# gdi+ gdi gif


    【解决方案1】:

    可能为时已晚,对原始海报有用,但我设法仅使用 System.Drawing 创建了一个合适的 gif。下面的代码基于 jschroedl 的回答,但也设置了帧延迟和动画循环次数。

    // Gdi+ constants absent from System.Drawing.
    const int PropertyTagFrameDelay = 0x5100;
    const int PropertyTagLoopCount = 0x5101;
    const short PropertyTagTypeLong = 4;
    const short PropertyTagTypeShort = 3;
    
    const inr UintBytes = 4;
    
    //...
    
    var gifEncoder = GetEncoder(ImageFormat.Gif);
    // Params of the first frame.
    var encoderParams1 = new EncoderParameters(1);
    encoderParams1.Param[0] = new EncoderParameter(Encoder.SaveFlag, (long)EncoderValue.MultiFrame);
    // Params of other frames.
    var encoderParamsN = new EncoderParameters(1);
    encoderParamsN.Param[0] = new EncoderParameter(Encoder.SaveFlag, (long)EncoderValue.FrameDimensionTime);            
    // Params for the finalizing call.
    var encoderParamsFlush = new EncoderParameters(1);
    encoderParamsFlush.Param[0] = new EncoderParameter(Encoder.SaveFlag, (long)EncoderValue.Flush);
    
    // PropertyItem for the frame delay (apparently, no other way to create a fresh instance).
    var frameDelay = (PropertyItem)FormatterServices.GetUninitializedObject(typeof(PropertyItem));
    frameDelay.Id = PropertyTagFrameDelay;
    frameDelay.Type = PropertyTagTypeLong;
    // Length of the value in bytes.
    frameDelay.Len = Bitmaps.Count * UintBytes;
    // The value is an array of 4-byte entries: one per frame.
    // Every entry is the frame delay in 1/100-s of a second, in little endian.
    frameDelay.Value = new byte[Bitmaps.Count * UintBytes];
    // E.g., here, we're setting the delay of every frame to 1 second.
    var frameDelayBytes = BitConverter.GetBytes((uint)100);
    for (int j = 0; j < Bitmaps.Count; ++j)
        Array.Copy(frameDelayBytes, 0, frameDelay.Value, j * UintBytes, UintBytes);
    
    // PropertyItem for the number of animation loops.
    var loopPropertyItem = (PropertyItem)FormatterServices.GetUninitializedObject(typeof(PropertyItem));
    loopPropertyItem.Id = PropertyTagLoopCount;
    loopPropertyItem.Type = PropertyTagTypeShort;
    loopPropertyItem.Len = 1;
    // 0 means to animate forever.
    loopPropertyItem.Value = BitConverter.GetBytes((ushort)0);
    
    using (var stream = new FileStream("animation.gif", FileMode.Create))
    {
        bool first = true;
        Bitmap firstBitmap = null;
        // Bitmaps is a collection of Bitmap instances that'll become gif frames.
        foreach (var bitmap in Bitmaps)
        {
            if (first)
            {
                firstBitmap = bitmap;
                firstBitmap.SetPropertyItem(frameDelay);
                firstBitmap.SetPropertyItem(loopPropertyItem);
                firstBitmap.Save(stream, gifEncoder, encoderParams1);
                first = false;
            }
            else
            {
                firstBitmap.SaveAdd(bitmap, encoderParamsN);
            }
        }
        firstBitmap.SaveAdd(encoderParamsFlush);
    }
    
    // ...
    
    private ImageCodecInfo GetEncoder(ImageFormat format)
    {
        ImageCodecInfo[] codecs = ImageCodecInfo.GetImageDecoders();
        foreach (ImageCodecInfo codec in codecs)
        {
            if (codec.FormatID == format.Guid)
            {
                return codec;
            }
        }
        return null;
    }
    

    【讨论】:

    • 感谢 Alexey,这似乎运作良好。我将您的代码包装到一个类定义中,并为它添加了一个静态测试过程。使用:AnimatedGif.TestFlickering()
    • 我必须做出的一项更改涉及循环属性。事实证明,如果您执行值为 0 的 SetPropertyItem(loopPropertyItem),则 GIF 将按预期无休止地旋转。但是当这个值设置为 1 时,结果将是一个循环 1x 的 GIF(执行 两次 !)。让 GIF 不循环(执行一次!)的唯一方法是不设置循环属性。所以我包括了一个测试。
    【解决方案2】:

    我在这些参数上取得了成功。 img1,2,3,4...是我要合并的图像。

    ULONG parameterValue;
    
    EncoderParameters encoderParameters;
    encoderParameters.Count = 1;
    
    encoderParameters.Parameter[0].Guid = EncoderSaveFlag;
    encoderParameters.Parameter[0].Type = EncoderParameterValueTypeLong;
    encoderParameters.Parameter[0].NumberOfValues = 1;
    encoderParameters.Parameter[0].Value = &parameterValue;
    
    // Save the first frame
    parameterValue = EncoderValueMultiFrame;
    rc = img1->Save(L"Output.gif", &encoderClsid, &encoderParameters);
    assert(rc == Ok);
    
    // Add the second frame
    parameterValue = EncoderValueFrameDimensionTime;
    rc = img1->SaveAdd(img2, &encoderParameters);
    assert(rc == Ok);
    
    // etc...adding frames  img3,4,5...
    
    // Done...
    parameterValue = EncoderValueFlush;
    rc = img1->SaveAdd(&encoderParameters);
    assert(rc == Ok);
    

    编辑:我刚刚意识到您要求提供 C#,而我有 C++ 代码。希望参数仍然适用。

    【讨论】:

    • 虽然这个 sn-p 确实创建了一个多帧 gif,但它没有设置帧延迟和动画循环的数量。我将在单独的答案中发布我的代码(执行此操作)。
    【解决方案3】:

    如果您想创建包含许多图片的 gif,您可以使用 ngif。见this

    //you should replace filepath
    String [] imageFilePaths = new String[]{"c:\\01.png","c:\\02.png","c:\\03.png"}; 
    String outputFilePath = "c:\\test.gif";
    AnimatedGifEncoder e = new AnimatedGifEncoder();
    e.Start( outputFilePath );
    e.SetDelay(500);
    //-1:no repeat,0:always repeat
    e.SetRepeat(0);
    for (int i = 0, count = imageFilePaths.Length; i < count; i++ ) 
    {
     e.AddFrame( Image.FromFile( imageFilePaths[i] ) );
    }
    e.Finish();
    /* extract Gif */
    string outputPath = "c:\\";
    GifDecoder gifDecoder = new GifDecoder();
    gifDecoder.Read( "c:\\test.gif" );
    for ( int i = 0, count = gifDecoder.GetFrameCount(); i < count; i++ ) 
    {
     Image frame = gifDecoder.GetFrame( i ); // frame i
     frame.Save( outputPath + Guid.NewGuid().ToString() 
                           + ".png", ImageFormat.Png );
    }
    

    【讨论】:

    • 这个demo我知道,但是我想用Image.SaveAdd方法
    猜你喜欢
    • 1970-01-01
    • 2015-11-21
    • 2015-04-06
    • 2019-10-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-06-04
    • 1970-01-01
    相关资源
    最近更新 更多