增加GIF导出格式

This commit is contained in:
ww-rm
2025-03-26 13:10:51 +08:00
parent 17904326f3
commit 99d81c4329
2 changed files with 127 additions and 0 deletions

View File

@@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SpineViewer.Exporter.Implementations.ExportArgs
{
/// <summary>
/// GIF 导出参数
/// </summary>
[ExportImplementation(ExportType.GIF)]
public class GifExportArgs : VideoExportArgs
{
public GifExportArgs(Size resolution, SFML.Graphics.View view, bool renderSelectedOnly) : base(resolution, view, renderSelectedOnly)
{
// GIF 的帧率不能太高, 超过 50 帧反而会变慢
FPS = 25;
}
/// <summary>
/// 调色板最大颜色数量
/// </summary>
[Category("GIF 参数"), DisplayName("调色板最大颜色数量"), Description("设置调色板使用的最大颜色数量, 越多则色彩保留程度越高")]
public uint MaxColors { get => maxColors; set => maxColors = Math.Clamp(value, 2, 256); }
private uint maxColors = 256;
/// <summary>
/// 透明度阈值
/// </summary>
[Category("GIF 参数"), DisplayName("透明度阈值"), Description("小于该值的像素点会被认为是透明像素")]
public byte AlphaThreshold { get => alphaThreshold; set => alphaThreshold = value; }
private byte alphaThreshold = 128;
/// <summary>
/// 获取构造好的 FFMpegCore 自定义参数
/// </summary>
[Browsable(false)]
public string FFMpegCoreCustomArguments
{
get
{
var v = $"[0:v] split [s0][s1]";
var s0 = $"[s0] palettegen=reserve_transparent=1:max_colors={MaxColors} [p]";
var s1 = $"[s1][p] paletteuse=dither=bayer:alpha_threshold={AlphaThreshold}";
return $"-filter_complex \"{v};{s0};{s1}\"";
}
}
}
}

View File

@@ -0,0 +1,76 @@
using FFMpegCore.Pipes;
using FFMpegCore;
using SpineViewer.Exporter.Implementations.ExportArgs;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FFMpegCore.Arguments;
namespace SpineViewer.Exporter.Implementations.Exporter
{
/// <summary>
/// GIF 动图导出器
/// </summary>
[ExportImplementation(ExportType.GIF)]
public class GifExporter : VideoExporter
{
public GifExporter(GifExportArgs exportArgs) : base(exportArgs) { }
protected override void ExportSingle(Spine.Spine[] spinesToRender, BackgroundWorker? worker = null)
{
var args = (GifExportArgs)ExportArgs;
// 导出单个时必定提供输出文件夹
var filename = $"{timestamp}_{args.FPS:f0}_{args.MaxColors}_{args.AlphaThreshold}.gif";
var savePath = Path.Combine(args.OutputDir, filename);
var videoFramesSource = new RawVideoPipeSource(GetFrames(spinesToRender, worker)) { FrameRate = args.FPS };
try
{
FFMpegArguments
.FromPipeInput(videoFramesSource)
.OutputToFile(savePath, true, options => options
.ForceFormat("gif")
.WithCustomArgument(args.FFMpegCoreCustomArguments))
.ProcessSynchronously();
}
catch (Exception ex)
{
Program.Logger.Error(ex.ToString());
Program.Logger.Error("Failed to export gif {}", savePath);
}
}
protected override void ExportIndividual(Spine.Spine[] spinesToRender, BackgroundWorker? worker = null)
{
var args = (GifExportArgs)ExportArgs;
foreach (var spine in spinesToRender)
{
if (worker?.CancellationPending == true) break; // 取消的日志在 GetFrames 里输出
// 如果提供了输出文件夹, 则全部导出到输出文件夹, 否则导出到各自的文件夹下
var filename = $"{spine.Name}_{timestamp}_{args.FPS:f0}_{args.MaxColors}_{args.AlphaThreshold}.gif";
var savePath = Path.Combine(args.OutputDir ?? spine.AssetsDir, filename);
var videoFramesSource = new RawVideoPipeSource(GetFrames(spine, worker)) { FrameRate = args.FPS };
try
{
FFMpegArguments
.FromPipeInput(videoFramesSource)
.OutputToFile(savePath, true, options => options
.ForceFormat("gif")
.WithCustomArgument(args.FFMpegCoreCustomArguments))
.ProcessSynchronously();
}
catch (Exception ex)
{
Program.Logger.Error(ex.ToString());
Program.Logger.Error("Failed to export gif {} {}", savePath, spine.SkelPath);
}
}
}
}
}