You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C#中将.gif文件转换为.ani文件并保留所有帧

在C#中将GIF动画转换为ANI并保留所有帧

要实现GIF到ANI的完整帧转换,核心是解析GIF的帧数据与动画参数,再按照ANI文件的规范重新封装这些内容。以下是具体实现步骤和代码示例:

核心前提

ANI是Windows动画光标格式,由多个ICO帧、延迟时间、循环设置组成。要完整保留GIF帧,需确保:

  • 提取GIF的每帧图像(包括透明通道)
  • 转换GIF的帧延迟(百分之一秒)为ANI的毫秒延迟
  • 继承GIF的循环设置(无限循环或指定次数)

实现步骤(使用ImageSharp跨平台库)

1. 安装依赖

在项目中安装SixLabors.ImageSharp和SixLabors.ImageSharp.Formats.Gif包:

dotnet add package SixLabors.ImageSharp
dotnet add package SixLabors.ImageSharp.Formats.Gif

2. 读取GIF的帧与动画参数

using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Gif;
using SixLabors.ImageSharp.PixelFormats;

// 读取GIF文件
var gifPath = "input.gif";
using var gifImage = Image.Load<Rgba32>(gifPath, out var gifDecoder);
var gifMetadata = gifImage.Metadata.GetGifMetadata();

// 提取所有帧及延迟时间(GIF延迟单位为1/100秒,需转为毫秒)
var frames = new List<(Image<Rgba32> Frame, int DelayMs)>();
foreach (var frame in gifImage.Frames)
{
    var frameMetadata = frame.Metadata.GetGifMetadata();
    int delayMs = frameMetadata.DelayTime * 10; // 转换为毫秒
    frames.Add((frame.Clone(), delayMs));
}

// 获取循环次数:0表示无限循环
int loopCount = gifMetadata.RepeatCount;

3. 构建并写入ANI文件

ANI文件的结构包含ACON主块、LIST帧列表块,以及每个帧的ICON数据。以下是简化的写入实现:

using System.IO;
using System.Buffers.Binary;

// 定义ANI头结构(简化版)
struct AniHeader
{
    public uint Signature; // "RIFF"
    public uint FileSize;
    public uint AniSignature; // "ACON"
    public uint ListSignature; // "LIST"
    public uint ListSize;
    public uint InfoSignature; // "INFO"
    public uint InfoSize;
    public ushort Version;
    public ushort Flags;
    public ushort NumFrames;
    public ushort NumSteps;
    public ushort Width;
    public ushort Height;
    public ushort BitCount;
    public ushort Planes;
    public int DisplayRate; // 单帧延迟,若各帧延迟不同需扩展FRAM块
    public int LoopCount;
}

// 写入ANI文件
var aniPath = "output.ani";
using var fs = new FileStream(aniPath, FileMode.Create);
using var writer = new BinaryWriter(fs);

// 计算基本参数
int frameCount = frames.Count;
var firstFrame = frames[0].Frame;
ushort width = (ushort)firstFrame.Width;
ushort height = (ushort)firstFrame.Height;

// 构建ANI头
var header = new AniHeader
{
    Signature = BinaryPrimitives.ReadUInt32LittleEndian(System.Text.Encoding.ASCII.GetBytes("RIFF")),
    FileSize = 0, // 后续计算
    AniSignature = BinaryPrimitives.ReadUInt32LittleEndian(System.Text.Encoding.ASCII.GetBytes("ACON")),
    ListSignature = BinaryPrimitives.ReadUInt32LittleEndian(System.Text.Encoding.ASCII.GetBytes("LIST")),
    ListSize = 0, // 后续计算
    InfoSignature = BinaryPrimitives.ReadUInt32LittleEndian(System.Text.Encoding.ASCII.GetBytes("INFO")),
    InfoSize = 36,
    Version = 0x0001,
    Flags = 0,
    NumFrames = (ushort)frameCount,
    NumSteps = (ushort)frameCount,
    Width = width,
    Height = height,
    BitCount = 32, // 保留32位透明通道
    Planes = 1,
    DisplayRate = frames[0].DelayMs, // 若各帧延迟不同,需单独处理FRAM块
    LoopCount = loopCount == 0 ? -1 : loopCount // ANI中-1表示无限循环
};

// 先写入头占位
writer.Write(header.Signature);
writer.Write(header.FileSize);
writer.Write(header.AniSignature);
writer.Write(header.ListSignature);
writer.Write(header.ListSize);
writer.Write(header.InfoSignature);
writer.Write(header.InfoSize);
writer.Write(header.Version);
writer.Write(header.Flags);
writer.Write(header.NumFrames);
writer.Write(header.NumSteps);
writer.Write(header.Width);
writer.Write(header.Height);
writer.Write(header.BitCount);
writer.Write(header.Planes);
writer.Write(header.DisplayRate);
writer.Write(header.LoopCount);

// 写入每个帧的ICO数据
long listStartPos = fs.Position - 4; // 记录LIST块位置,后续更新大小
foreach (var (frame, delay) in frames)
{
    // 将帧保存为ICO格式到内存流
    using var icoStream = new MemoryStream();
    frame.SaveAsIco(icoStream);
    icoStream.Seek(0, SeekOrigin.Begin);
    
    // 写入ICO数据到ANI
    icoStream.CopyTo(fs);
}

// 更新LIST块大小和文件总大小
long endPos = fs.Position;
long listSize = endPos - listStartPos - 4;
fs.Seek(listStartPos, SeekOrigin.Begin);
writer.Write((uint)listSize);

long fileSize = endPos - 8; // 减去RIFF的8字节
fs.Seek(4, SeekOrigin.Begin);
writer.Write((uint)fileSize);

关键注意事项

  • 延迟时间处理:GIF的延迟是每100分之一秒,需乘以10转为ANI的毫秒单位;若GIF各帧延迟不同,需为每个帧添加FRAM块单独指定延迟。
  • 透明通道保留:确保使用支持RGBA的图像库(如ImageSharp),避免丢失GIF的透明信息。
  • 跨平台兼容:若使用.NET Framework,也可以用System.Drawing读取GIF,但需注意GDI+在非Windows平台的限制。

内容的提问来源于stack exchange,提问作者Little Boy Blue

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.14 05:07:42