From b404d8e79a160512abd66eaae7a02242c62ac2cb Mon Sep 17 00:00:00 2001 From: ww-rm Date: Wed, 9 Apr 2025 15:24:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E6=9C=80=E5=90=8E=E4=B8=80?= =?UTF-8?q?=E5=B8=A7=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SpineViewer/Exporter/VideoExporter.cs | 67 ++++++++++++++++--- .../Exporter/VideoExporterWrapper.cs | 6 ++ 2 files changed, 64 insertions(+), 9 deletions(-) diff --git a/SpineViewer/Exporter/VideoExporter.cs b/SpineViewer/Exporter/VideoExporter.cs index 0b9d93d..0c99ae9 100644 --- a/SpineViewer/Exporter/VideoExporter.cs +++ b/SpineViewer/Exporter/VideoExporter.cs @@ -24,6 +24,11 @@ namespace SpineViewer.Exporter /// public float FPS { get; set; } = 60; + /// + /// 是否保留最后一帧 + /// + public bool KeepLast { get; set; } = true; + public override string? Validate() { if (base.Validate() is string error) @@ -43,9 +48,21 @@ namespace SpineViewer.Exporter if (duration < 0) duration = spine.GetTrackIndices().Select(i => spine.GetAnimationDuration(spine.GetAnimation(i))).Max(); float delta = 1f / FPS; - int total = Math.Max(1, (int)(duration * FPS)); // 至少导出 1 帧 + int total = (int)(duration * FPS); // 完整帧的数量 - worker?.ReportProgress(0, $"{spine.Name} 已处理 0/{total} 帧"); + float deltaFinal = duration - delta * total; // 最后一帧时长 + int final = (KeepLast && (deltaFinal > 1e-3)) ? 1 : 0; + + int frameCount = 1 + total + final; // 所有帧的数量 = 起始帧 + 完整帧 + 最后一帧 + + worker?.ReportProgress(0, $"{spine.Name} 已处理 0/{frameCount} 帧"); + + // 导出首帧 + var firstFrame = GetFrame(spine); + worker?.ReportProgress(1 * 100 / frameCount, $"{spine.Name} 已处理 1/{frameCount} 帧"); + yield return firstFrame; + + // 导出完整帧 for (int i = 0; i < total; i++) { if (worker?.CancellationPending == true) @@ -54,11 +71,20 @@ namespace SpineViewer.Exporter break; } - var frame = GetFrame(spine); spine.Update(delta); - worker?.ReportProgress((int)((i + 1) * 100.0) / total, $"{spine.Name} 已处理 {i + 1}/{total} 帧"); + var frame = GetFrame(spine); + worker?.ReportProgress((1 + i + 1) * 100 / frameCount, $"{spine.Name} 已处理 {1 + i + 1}/{frameCount} 帧"); yield return frame; } + + // 导出最后一帧 + if (final > 0) + { + spine.Update(deltaFinal); + var finalFrame = GetFrame(spine); + worker?.ReportProgress(100, $"{spine.Name} 已处理 {frameCount}/{frameCount} 帧"); + yield return finalFrame; + } } /// @@ -67,10 +93,24 @@ namespace SpineViewer.Exporter protected IEnumerable GetFrames(Spine.Spine[] spinesToRender, BackgroundWorker? worker = null) { // 导出单个时必须根据 Duration 决定导出时长 - float delta = 1f / FPS; - int total = Math.Max(1, (int)(Duration * FPS)); // 至少导出 1 帧 + var duration = Duration; - worker?.ReportProgress(0, $"已处理 0/{total} 帧"); + float delta = 1f / FPS; + int total = (int)(duration * FPS); // 完整帧的数量 + + float deltaFinal = duration - delta * total; // 最后一帧时长 + int final = (KeepLast && (deltaFinal > 1e-3)) ? 1 : 0; + + int frameCount = 1 + total + final; // 所有帧的数量 = 起始帧 + 完整帧 + 最后一帧 + + worker?.ReportProgress(0, $"已处理 0/{frameCount} 帧"); + + // 导出首帧 + var firstFrame = GetFrame(spinesToRender); + worker?.ReportProgress(1 * 100 / frameCount, $"已处理 1/{frameCount} 帧"); + yield return firstFrame; + + // 导出完整帧 for (int i = 0; i < total; i++) { if (worker?.CancellationPending == true) @@ -79,11 +119,20 @@ namespace SpineViewer.Exporter break; } - var frame = GetFrame(spinesToRender); foreach (var spine in spinesToRender) spine.Update(delta); - worker?.ReportProgress((int)((i + 1) * 100.0) / total, $"已处理 {i + 1}/{total} 帧"); + var frame = GetFrame(spinesToRender); + worker?.ReportProgress((1 + i + 1) * 100 / frameCount, $"已处理 {1 + i + 1}/{frameCount} 帧"); yield return frame; } + + // 导出最后一帧 + if (final > 0) + { + foreach (var spine in spinesToRender) spine.Update(delta); + var finalFrame = GetFrame(spinesToRender); + worker?.ReportProgress(100, $"已处理 {frameCount}/{frameCount} 帧"); + yield return finalFrame; + } } public override void Export(Spine.Spine[] spines, BackgroundWorker? worker = null) diff --git a/SpineViewer/PropertyGridWrappers/Exporter/VideoExporterWrapper.cs b/SpineViewer/PropertyGridWrappers/Exporter/VideoExporterWrapper.cs index e9f14d9..3becc3e 100644 --- a/SpineViewer/PropertyGridWrappers/Exporter/VideoExporterWrapper.cs +++ b/SpineViewer/PropertyGridWrappers/Exporter/VideoExporterWrapper.cs @@ -24,5 +24,11 @@ namespace SpineViewer.PropertyGridWrappers.Exporter /// [Category("[1] 视频参数"), DisplayName("帧率"), Description("每秒画面数")] public float FPS { get => Exporter.FPS; set => Exporter.FPS = value; } + + /// + /// 保留最后一帧 + /// + [Category("[1] 视频参数"), DisplayName("保留最后一帧"), Description("当设置保留最后一帧时, 动图会更为连贯, 但是帧数可能比预期帧数多 1")] + public bool KeepLast { get => Exporter.KeepLast; set => Exporter.KeepLast = value; } } }