我正在尝试将两个不同的 gif 文件合并为一个文件。
首先,我了解了很多关于gif格式的知识。我知道延迟时间值是在图形控制扩展中设置的,它是一个 gif 文件块。
我保存了第一个gif并设置了FrameDelay值,代码如下:
ImageCodecInfo codeInfo = GetEncoder(ImageFormat.Gif);
System.Drawing.Imaging.Encoder saveEncoder = System.Drawing.Imaging.Encoder.SaveFlag;
EncoderParameters parameters = new EncoderParameters(1);
parameters.Param[0] = new EncoderParameter(saveEncoder, (long)EncoderValue.MultiFrame);
PropertyItem PropertyTagFrameDelay = img1.GetPropertyItem(0x5100);
PropertyTagFrameDelay.Value = new byte[] { 0x96, 0x00 };// this is the delay value 0x0096, means 1.5 second
img1.SetPropertyItem(PropertyTagFrameDelay);
PropertyItem LoopCount = img1.GetPropertyItem(0x5101);
LoopCount.Value = new byte[] { 0x00, 0x00 };// this means the gif loops endlessly
img1.SetPropertyItem(LoopCount);
img1.Save(@"c:\ddd.gif", codeInfo, parameters);
然后我尝试添加另一张图像作为第二帧。
parameters = new EncoderParameters(1);
parameters.Param[0] = new EncoderParameter(saveEncoder, (long)EncoderValue.FrameDimensionTime);
PropertyTagFrameDelay = img2.GetPropertyItem(0x5100);
PropertyTagFrameDelay.Value = new byte[] { 0x96, 0x00 };// this is the delay value 0x0096, means 1.5 second
img2.SetPropertyItem(PropertyTagFrameDelay);
最后,我应该终止这个图像。
parameters = new EncoderParameters(1);
parameters.Param[0] = new EncoderParameter(saveEncoder, (long)EncoderValue.Flush);
img1.SaveAdd(parameters);
我发现第二帧的延迟时间始终为0。
我尝试了很多方法,但我不知道如何将其设为0x96。
那么这有什么问题吗?
任何 .NET 图像编码器都不支持此功能。 既不是通过 GDI+,也不是通过 WIC,System.Drawing.Bitmap
和
System.Windows.Media.Imaging.PngBitmapEncoder
类的底层本机编解码器。虽然这听起来像是一个非常奇怪的疏忽,但最可能的原因是 GIF 受到软件专利的阻碍。 Unisys 拥有 LZW 压缩算法的权利,并开始“积极寻求”获取其许可费用。 从最明显、最能赚到钱的目标开始,微软永远位居榜首。 他们也不谦虚,一个在网页上使用 GIF 的非商业或私人网站在 1999 年不得不付出五千
美元。 这杀死了图像格式。 在此之前,它们无处不在,几乎每个人都停止使用它们。 速度也快得惊人,只花了几个月的时间。 幸运的是,恰逢每个人都完全沉浸在动画 GIF 中,顺便说一句,之前的做法确实有些过头了。 你可能会在回程机器上找到一些当时的网页,眼角的一切都在移动。 这并不是唯一的幸运巧合,这也是开源 PNG 格式被开发的核心原因。 感谢我们的幸运星:)
该专利在 2004 年左右到期,具体取决于您居住的地方,因此您不必再担心 Unisys 的来信。 长话短说,您必须四处寻找另一个库才能将此功能添加到您的程序中。 这个现有的SO问题很好地涵盖了它,无需在这里重复。
如果您愿意使用第三方库,您可以使用Magick.NET。这是
ImageMagickusing (MagickImageCollection images = new MagickImageCollection())
{
MagickImage firstFrame = new MagickImage("first.gif");
firstFrame.AnimationDelay = 1500;
images.Add(firstFrame);
MagickImage secondFrame = new MagickImage("second.gif");
secondFrame.AnimationDelay = 200;
images.Add(secondFrame);
// This method will try to make your output image smaller.
images.OptimizePlus();
images.Write(@"c:\ddd.gif");
}
来自
:您无法使用 SaveAdd 将帧添加到动画 gif 文件中。
或 Stackoverflow 上的相同代码就是一个很好的示例。
我做了更多研究,我认为这些 ffmpeg 和 mplayer 推荐值得尝试:更新2:
来自 Rick van den Bosch
.Net(至少1.1,他们可能会将其合并到2.0中)没有给出 您可以通过 GDI+ 创建动画 GIF。
正如 Hans 提到的,它不受支持,所以第三种解决方案是 RenniePet 的建议,从两个 Gif 中提取帧,然后将所有帧组合在一起。添加对 System.Drawing.DLL 的引用并使用此代码获取框架:
using System.Drawing;
using System.Drawing.Imaging;
public class GifImage
{
private Image gifImage;
private FrameDimension dimension;
private int frameCount;
private int currentFrame = -1;
private bool reverse;
private int step = 1;
public GifImage(string path)
{
gifImage = Image.FromFile(path);
//initialize
dimension = new FrameDimension(gifImage.FrameDimensionsList[0]);
//gets the GUID
//total frames in the animation
frameCount = gifImage.GetFrameCount(dimension);
}
public int GetFrameCount()
{
return frameCount;
}
public bool ReverseAtEnd
{
//whether the gif should play backwards when it reaches the end
get { return reverse; }
set { reverse = value; }
}
public Image GetNextFrame()
{
currentFrame += step;
//if the animation reaches a boundary...
if (currentFrame >= frameCount || currentFrame < 1)
{
if (reverse)
{
step *= -1;
//...reverse the count
//apply it
currentFrame += step;
}
else
{
currentFrame = 0;
//...or start over
}
}
return GetFrame(currentFrame);
}
public Image GetFrame(int index)
{
gifImage.SelectActiveFrame(dimension, index);
//find the frame
return (Image)gifImage.Clone();
//return a copy of it
}
}
private static readonly string tempFolder = @"C:\temp\";
static void Main(string[] args)
{
CombineGifs(@"c:\temp\a.gif", @"c:\temp\b.gif");
}
public static void CombineGifs(string firstImageFilePath, string secondImageFilePath)
{
int frameCounter = ExtractGifFramesAndGetCount(firstImageFilePath, 0);
int secondframeCounter = ExtractGifFramesAndGetCount(secondImageFilePath, frameCounter);
string filePathOfCombinedGif = CombineFramesIntoGif(0, secondframeCounter);
}
private static int ExtractGifFramesAndGetCount(string filePath, int imageNameStartNumber)
{
////NGif had an error when I tried it
//GifDecoder gifDecoder = new GifDecoder();
//gifDecoder.Read(filePath);
//int frameCounter = imageNameStartNumber + gifDecoder.GetFrameCount();
//for (int i = imageNameStartNumber; i < frameCounter; i++)
//{
// Image frame = gifDecoder.GetFrame(i); // frame i
// frame.Save(tempFolder + i.ToString() + ".png", ImageFormat.Png);
//}
//So we'll use the Gifimage implementation
GifImage gifImage = new GifImage(filePath);
gifImage.ReverseAtEnd = false;
int frameCounter = imageNameStartNumber + gifImage.GetFrameCount();
for (int i = imageNameStartNumber; i < frameCounter; i++)
{
Image img = gifImage.GetNextFrame();
img.Save(tempFolder + i.ToString() + ".png");
}
return frameCounter;
}
接下来,我们使用
NGif
将所有帧组合成一个动画 gif
下载代码,打开解决方案并编译组件项目以获取 DLLGif.Components.dll
并在解决方案中引用该 DLL。
所有帧延迟值需要连接并设置在第一帧上。
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
internal static class Program
{
public static void Main()
{
var redImage = new Bitmap(64, 64);
using (var g = Graphics.FromImage(redImage))
g.Clear(Color.Red);
var greenImage = new Bitmap(64, 64);
using (var g = Graphics.FromImage(greenImage))
g.Clear(Color.Green);
var blueImage = new Bitmap(64, 64);
using (var g = Graphics.FromImage(blueImage))
g.Clear(Color.Blue);
using (var stream = new FileStream("test.gif", FileMode.Create, FileAccess.Write))
WriteAnimatedGif(stream, new (Image Image, uint Delay)[]
{
(redImage, 20u),
(greenImage, 60u),
(blueImage, 180u),
});
}
private static void WriteAnimatedGif(Stream stream, (Image Image, uint Delay)[] frames)
{
var frameDelayProperty = (PropertyItem)Activator.CreateInstance(typeof(PropertyItem), nonPublic: true);
frameDelayProperty.Id = (int)PropertyTag.FrameDelay;
frameDelayProperty.Len = frames.Length * sizeof(uint);
frameDelayProperty.Type = (int)PropertyTagType.Long;
frameDelayProperty.Value = new byte[frameDelayProperty.Len];
byte[] frameDelayData = frameDelayProperty.Value;
for (int i = 0; i < frames.Length; ++i)
BitConverter.GetBytes(frames[i].Delay).CopyTo(frameDelayData, i * sizeof(uint));
var loopCountProperty = (PropertyItem)Activator.CreateInstance(typeof(PropertyItem), nonPublic: true);
loopCountProperty.Id = (int)PropertyTag.LoopCount;
loopCountProperty.Len = sizeof(ushort);
loopCountProperty.Type = (short)PropertyTagType.Short;
loopCountProperty.Value = BitConverter.GetBytes((ushort)0);
var firstImage = frames[0].Image;
firstImage.SetPropertyItem(frameDelayProperty);
firstImage.SetPropertyItem(loopCountProperty);
var encoder = FindGifEncoder();
using (var encoderParameters = new EncoderParameters(1))
{
using (var saveFlagParameter = new EncoderParameter(Encoder.SaveFlag, (uint)EncoderValue.MultiFrame))
{
encoderParameters.Param[0] = saveFlagParameter;
firstImage.Save(stream, encoder, encoderParameters);
}
for (int i = 1; i < frames.Length; ++i)
{
var frameBitmap = frames[i].Image;
using (var saveFlagParameter = new EncoderParameter(Encoder.SaveFlag, (uint)EncoderValue.FrameDimensionTime))
{
encoderParameters.Param[0] = saveFlagParameter;
firstImage.SaveAdd(frameBitmap, encoderParameters);
}
}
using (var saveFlagParameter = new EncoderParameter(Encoder.SaveFlag, (uint)EncoderValue.Flush))
{
encoderParameters.Param[0] = saveFlagParameter;
firstImage.SaveAdd(encoderParameters);
}
}
}
private static ImageCodecInfo FindGifEncoder()
{
foreach (var encoder in ImageCodecInfo.GetImageEncoders())
if (encoder.FormatID == ImageFormat.Gif.Guid)
return encoder;
throw new InvalidOperationException("Failed to find GIF encoder.");
}
private enum PropertyTagType
{
Short = 3, // ushort
Long = 4, // uint
}
private enum PropertyTag
{
FrameDelay = 0x5100,
LoopCount = 0x5101,
}
}
但是无法设置帧处理方法,因此如果您尝试创建具有透明度的动画 GIF,请放弃所有希望。