C# .net core中如何将多张png图片合并成一个gif

发布时间 2023-07-27 14:42:27作者: 乔达摩(嘿~)

背景

我们有很多这样的序列帧:

image-20230727114152382

我这边要把这些序列帧裁切最后合并成gif,以下是我裁切后的png文件:

image-20230727115247029

我一开始选用的是 SixLabors.ImageSharp

这是裁切代码:

using var image = Image.Load("input.jpg");
image.Clone(x => x.Crop(new Rectangle(10, 10, 250, 250)));
image.Save("output.jpg");

gif合成方案1(SixLabors.ImageSharp)

这里直接给出核心代码

public string FrameCombine(List<FrameConfig> frames, int fps)
{
    Image firstFrame = null;
    var delay = 100 / fps; //根据帧率技术延迟
    GifDisposalMethod disposalMethod = GifDisposalMethod.RestoreToBackground; //背景处理方式
    string outputPath = null;

    for (int i = 0; i < frames.Count; i++)
    {
        Image tempImage = Image.Load(frames[i].Path);
        if (i == 0) //第一帧做底图
        {
            outputPath = Path.Combine(Path.GetDirectoryName(frames[i].Path), "sticker.gif");
            if (File.Exists(outputPath))
            {
                return null;
            }

            firstFrame = tempImage;
            firstFrame.Frames.RootFrame.Metadata.GetGifMetadata().FrameDelay = delay;
            firstFrame.Metadata.GetGifMetadata().RepeatCount = 0;
        }
        else
        {
        	//把其他帧合到第一帧上
            firstFrame.Frames.AddFrame(tempImage.Frames.RootFrame);
            var meta = firstFrame.Frames[i].Metadata.GetGifMetadata();
            meta.FrameDelay = delay;
            meta.DisposalMethod = disposalMethod;
        }
    }

    firstFrame.SaveAsGif(outputPath);

    return outputPath;
}

最后合成效果(都多多少少有点问题)

大致显示正常(但锯齿和毛边严重)

sticker

还有这样的(带莫名的绿色噪点/绿底等):

sticker

sticker

这样的(莫名灰底):

sticker

试了很多方方法,想尽办法调各种属性都不行,看来用SixLabors.ImageSharp比较难解决了;

gif合成方案2(FFmpeg)--推荐

前面SixLabors.ImageSharp方案生成的gif太多问题了,最终是用FFmpeg重新合成才实现的。

步骤

首先,为所有图片生成一个统一的调色板:

ffmpeg -i %02d.png -vf "palettegen" palette.png

然后,使用这个调色板的颜色为基础来生成GIF:

ffmpeg -r 16 -i  %02d.png  -i palette.png -lavfi paletteuse sticker.gif

-r 16 :帧率
-i palette.png :是用于为GIF提供颜色调色板的图像。
-lavfi paletteuse:这是一个复杂的滤镜图描述,指示ffmpeg如何处理输入内容。paletteuse是一个特定的滤镜,它使用前面的name.png输入作为源来生成一个调色板,并使用这个调色板来处理其他输入(在本例中即img_%d.png匹配到的文件)。

将这两条命令合成一条

ffmpeg  -r 16 -i %02d.png -filter_complex "palettegen=stats_mode=single[pal],[0:v][pal]paletteuse" sticker.gif

C#写法(用了这个执行控制台命令的nuget CliWrap

var workDir = Path.GetDirectoryName(frames[0].Path);
var outputPath = Path.Combine(workDir, "sticker.gif");
var param = $" -r {fps} -i %02d.png -filter_complex \"palettegen=stats_mode=single[pal],[0:v][pal]paletteuse\" {outputPath} -y";

try
{
    var result = await Cli.Wrap("ffmpeg").WithArguments(param).WithWorkingDirectory(workDir).ExecuteBufferedAsync();

    if (result.ExitCode == 0)
    {
        return outputPath;
    }

    return outputPath;
}
catch (Exception ex)
{
    logger.LogError(ex, "FrameCombine2 failed:{0}", frames[0]?.Path);
}

最后展示效果

sticker

sticker

sticker

总结

有的时候其实是比较简单的问题,但如果思路限制在C#的话可能还是比较麻烦的,要去一个个图片处理库试了;