如何使用C#创建一个gif图像

3
我想创建一个包含多个帧的gif文件。 我想使用微软支持的方法--Image.SaveAdd。 但我不知道如何设置EncoderParameters参数来组成gif文件。 我找不到相关文档参考。那么如何使用Image.SaveAdd创建gif文件呢?
3个回答

5

也许对于原帖作者来说已经太晚了,但是我成功地使用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() - Goodies
我需要做的一个更改涉及到循环属性。事实证明,如果您使用值0执行SetPropertyItem(loopPropertyItem),则GIF将无限旋转,正如预期的那样。但是,当此值设置为1时,结果将是循环1次的GIF(执行两次!)。让GIF不循环(只执行一次!)的唯一方法是不设置循环属性。因此,我包括了一个测试。 - Goodies

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 ++代码。希望参数仍然适用。


虽然这段代码片段确实创建了一个多帧gif,但它没有设置帧延迟和动画循环的次数。我会在另一个答案中发布我的代码(它可以做到这一点)。 - Alexey B.

1

如果您想使用多张图片创建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 );
}

我知道这个演示。但我想使用Image.SaveAdd方法。 - roast_soul

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接