From e2a84d8f8889d7c3b7e1e435cac51f4ac78bfcb1 Mon Sep 17 00:00:00 2001 From: ww-rm Date: Fri, 11 Apr 2025 00:58:25 +0800 Subject: [PATCH] small change --- SpineViewer/Controls/SpinePreviewer.cs | 40 +++++++++++- SpineViewer/Dialogs/ExportDialog.cs | 8 +-- SpineViewer/Exporter/AvifExporter.cs | 55 ---------------- SpineViewer/Exporter/CustomExporter.cs | 36 ----------- SpineViewer/Exporter/MkvExporter.cs | 49 --------------- SpineViewer/Exporter/MovExporter.cs | 48 -------------- SpineViewer/Exporter/Mp4Exporter.cs | 49 --------------- SpineViewer/Exporter/WebmExporter.cs | 50 --------------- SpineViewer/Exporter/WebpExporter.cs | 60 ------------------ SpineViewer/Forms/SpineViewerForm.cs | 28 ++++----- .../Exporter/ExporterWrapper.cs | 56 ----------------- .../Exporter/FFmpegVideoExporterWrapper.cs | 34 ---------- .../Exporter/FrameExporterWrapper.cs | 37 ----------- .../Exporter/FrameSequenceExporterWrapper.cs | 23 ------- .../Exporter/GifExporterWrapper.cs | 34 ---------- .../Exporter/VideoExporterWrapper.cs | 34 ---------- .../Spine/SpineAnimationWrapper.cs | 1 + .../Spine/SpineBaseInfoWrapper.cs | 1 + .../Spine/SpineSkinWrapper.cs | 1 + .../Spine/SpineTransformWrapper.cs | 1 + .../SpinePreviewerWrapper.cs | 48 -------------- .../SpineExporter/AvifExporter.cs} | 52 +++++++++++++++- .../SpineExporter/CustomExporter.cs} | 34 ++++++++-- .../SpineExporter}/ExportHelper.cs | 2 +- .../SpineExporter}/Exporter.cs | 62 ++++++++++++++++--- .../SpineExporter}/FFmpegVideoExporter.cs | 30 ++++++++- .../SpineExporter}/FrameExporter.cs | 32 +++++++++- .../SpineExporter}/FrameSequenceExporter.cs | 20 +++++- .../SpineExporter}/GifExporter.cs | 26 +++++++- .../SpineExporter/MkvExporter.cs} | 46 +++++++++++++- .../SpineExporter/MovExporter.cs} | 45 +++++++++++++- .../SpineExporter/Mp4Exporter.cs} | 46 +++++++++++++- .../SpineExporter}/VideoExporter.cs | 36 +++++++++-- .../SpineExporter/WebmExporter.cs} | 47 +++++++++++++- .../SpineExporter/WebpExporter.cs} | 57 ++++++++++++++++- SpineViewer/Utils/ImplementationResolver.cs | 3 +- .../TypeConverter.cs | 2 +- .../UITypeEditor.cs | 2 +- 38 files changed, 552 insertions(+), 683 deletions(-) delete mode 100644 SpineViewer/Exporter/AvifExporter.cs delete mode 100644 SpineViewer/Exporter/CustomExporter.cs delete mode 100644 SpineViewer/Exporter/MkvExporter.cs delete mode 100644 SpineViewer/Exporter/MovExporter.cs delete mode 100644 SpineViewer/Exporter/Mp4Exporter.cs delete mode 100644 SpineViewer/Exporter/WebmExporter.cs delete mode 100644 SpineViewer/Exporter/WebpExporter.cs delete mode 100644 SpineViewer/PropertyGridWrappers/Exporter/ExporterWrapper.cs delete mode 100644 SpineViewer/PropertyGridWrappers/Exporter/FFmpegVideoExporterWrapper.cs delete mode 100644 SpineViewer/PropertyGridWrappers/Exporter/FrameExporterWrapper.cs delete mode 100644 SpineViewer/PropertyGridWrappers/Exporter/FrameSequenceExporterWrapper.cs delete mode 100644 SpineViewer/PropertyGridWrappers/Exporter/GifExporterWrapper.cs delete mode 100644 SpineViewer/PropertyGridWrappers/Exporter/VideoExporterWrapper.cs delete mode 100644 SpineViewer/PropertyGridWrappers/SpinePreviewerWrapper.cs rename SpineViewer/{PropertyGridWrappers/Exporter/AvifExporterWrapper.cs => Spine/SpineExporter/AvifExporter.cs} (54%) rename SpineViewer/{PropertyGridWrappers/Exporter/CustomExporterWrapper.cs => Spine/SpineExporter/CustomExporter.cs} (52%) rename SpineViewer/{Exporter => Spine/SpineExporter}/ExportHelper.cs (98%) rename SpineViewer/{Exporter => Spine/SpineExporter}/Exporter.cs (70%) rename SpineViewer/{Exporter => Spine/SpineExporter}/FFmpegVideoExporter.cs (73%) rename SpineViewer/{Exporter => Spine/SpineExporter}/FrameExporter.cs (70%) rename SpineViewer/{Exporter => Spine/SpineExporter}/FrameSequenceExporter.cs (74%) rename SpineViewer/{Exporter => Spine/SpineExporter}/GifExporter.cs (57%) rename SpineViewer/{PropertyGridWrappers/Exporter/MkvExporterWrapper.cs => Spine/SpineExporter/MkvExporter.cs} (53%) rename SpineViewer/{PropertyGridWrappers/Exporter/MovExporterWrapper.cs => Spine/SpineExporter/MovExporter.cs} (54%) rename SpineViewer/{PropertyGridWrappers/Exporter/Mp4ExporterWrapper.cs => Spine/SpineExporter/Mp4Exporter.cs} (52%) rename SpineViewer/{Exporter => Spine/SpineExporter}/VideoExporter.cs (75%) rename SpineViewer/{PropertyGridWrappers/Exporter/WebmExporterWrapper.cs => Spine/SpineExporter/WebmExporter.cs} (52%) rename SpineViewer/{PropertyGridWrappers/Exporter/WebpExporterWrapper.cs => Spine/SpineExporter/WebpExporter.cs} (53%) rename SpineViewer/{PropertyGridWrappers => Utils}/TypeConverter.cs (99%) rename SpineViewer/{PropertyGridWrappers => Utils}/UITypeEditor.cs (99%) diff --git a/SpineViewer/Controls/SpinePreviewer.cs b/SpineViewer/Controls/SpinePreviewer.cs index cce1102..1567924 100644 --- a/SpineViewer/Controls/SpinePreviewer.cs +++ b/SpineViewer/Controls/SpinePreviewer.cs @@ -51,7 +51,7 @@ namespace SpineViewer.Controls { propertyGrid = value; if (propertyGrid is not null) - propertyGrid.SelectedObject = new PropertyGridWrappers.SpinePreviewerWrapper(this); + propertyGrid.SelectedObject = new SpinePreviewerProperty(this); } } private PropertyGrid? propertyGrid; @@ -635,4 +635,42 @@ namespace SpineViewer.Controls //public void ClickForwardStepButton() => button_ForwardStep_Click(button_ForwardStep, EventArgs.Empty); //public void ClickForwardFastButton() => button_ForwardFast_Click(button_ForwardFast, EventArgs.Empty); } + + /// + /// 用于在 PropertyGrid 上显示 SpinePreviewe 属性的包装类, 提供用户操作接口 + /// + public class SpinePreviewerProperty(SpinePreviewer previewer) + { + [Browsable(false)] + public SpinePreviewer Previewer { get; } = previewer; + + [TypeConverter(typeof(SizeConverter))] + [Category("[0] 导出"), DisplayName("分辨率")] + public Size Resolution { get => Previewer.Resolution; set => Previewer.Resolution = value; } + + [TypeConverter(typeof(PointFConverter))] + [Category("[0] 导出"), DisplayName("画面中心点")] + public PointF Center { get => Previewer.Center; set => Previewer.Center = value; } + + [Category("[0] 导出"), DisplayName("缩放")] + public float Zoom { get => Previewer.Zoom; set => Previewer.Zoom = value; } + + [Category("[0] 导出"), DisplayName("旋转")] + public float Rotation { get => Previewer.Rotation; set => Previewer.Rotation = value; } + + [Category("[0] 导出"), DisplayName("水平翻转")] + public bool FlipX { get => Previewer.FlipX; set => Previewer.FlipX = value; } + + [Category("[0] 导出"), DisplayName("垂直翻转")] + public bool FlipY { get => Previewer.FlipY; set => Previewer.FlipY = value; } + + [Category("[0] 导出"), DisplayName("仅渲染选中")] + public bool RenderSelectedOnly { get => Previewer.RenderSelectedOnly; set => Previewer.RenderSelectedOnly = value; } + + [Category("[1] 预览"), DisplayName("显示坐标轴")] + public bool ShowAxis { get => Previewer.ShowAxis; set => Previewer.ShowAxis = value; } + + [Category("[1] 预览"), DisplayName("最大帧率")] + public uint MaxFps { get => Previewer.MaxFps; set => Previewer.MaxFps = value; } + } } diff --git a/SpineViewer/Dialogs/ExportDialog.cs b/SpineViewer/Dialogs/ExportDialog.cs index a5af9a4..17248b2 100644 --- a/SpineViewer/Dialogs/ExportDialog.cs +++ b/SpineViewer/Dialogs/ExportDialog.cs @@ -1,5 +1,4 @@ -using SpineViewer.PropertyGridWrappers.Exporter; -using SpineViewer.Utils; +using SpineViewer.Utils; using System; using System.Collections; using System.Collections.Generic; @@ -8,14 +7,15 @@ using System.Data; using System.Diagnostics; using System.Linq; using System.Reflection; +using SpineViewer.Spine.SpineExporter; namespace SpineViewer.Dialogs { public partial class ExportDialog : Form { - private readonly ExporterWrapper wrapper; + private readonly ExporterProperty wrapper; - public ExportDialog(ExporterWrapper wrapper) + public ExportDialog(ExporterProperty wrapper) { InitializeComponent(); this.wrapper = wrapper; diff --git a/SpineViewer/Exporter/AvifExporter.cs b/SpineViewer/Exporter/AvifExporter.cs deleted file mode 100644 index ea071e5..0000000 --- a/SpineViewer/Exporter/AvifExporter.cs +++ /dev/null @@ -1,55 +0,0 @@ -using FFMpegCore; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SpineViewer.Exporter -{ - /// - /// MP4 导出参数 - /// - public class AvifExporter : FFmpegVideoExporter - { - public AvifExporter() - { - FPS = 24; - } - - public override string Format => "avif"; - - public override string Suffix => ".avif"; - - /// - /// 编码器 - /// - public string Codec { get; set; } = "av1_nvenc"; - - /// - /// CRF - /// - public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); } - private int crf = 23; - - /// - /// 像素格式 - /// - public string PixelFormat { get; set; } = "yuv420p"; - - /// - /// 循环次数, 0 无限循环, 取值范围 [0, 65535] - /// - public int Loop { get => loop; set => loop = Math.Clamp(value, 0, 65535); } - private int loop = 0; - - public override string FileNameNoteSuffix => $"{Codec}_{CRF}_{PixelFormat}"; - - public override void SetOutputOptions(FFMpegArgumentOptions options) - { - base.SetOutputOptions(options); - options.WithVideoCodec(Codec).ForcePixelFormat(PixelFormat).WithConstantRateFactor(CRF).WithCustomArgument($"-loop {Loop}"); - } - } -} diff --git a/SpineViewer/Exporter/CustomExporter.cs b/SpineViewer/Exporter/CustomExporter.cs deleted file mode 100644 index 408e0cf..0000000 --- a/SpineViewer/Exporter/CustomExporter.cs +++ /dev/null @@ -1,36 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SpineViewer.Exporter -{ - /// - /// FFmpeg 自定义视频导出参数 - /// - public class CustomExporter : FFmpegVideoExporter - { - public CustomExporter() - { - CustomArgument = "-c:v libx264 -crf 23 -pix_fmt yuv420p"; // 提供一个示例参数 - } - - public override string Format => CustomFormat; - - public override string Suffix => CustomSuffix; - - public override string FileNameNoteSuffix => string.Empty; - - /// - /// 文件格式 - /// - public string CustomFormat { get; set; } = "mp4"; - - /// - /// 文件名后缀 - /// - public string CustomSuffix { get; set; } = ".mp4"; - } -} diff --git a/SpineViewer/Exporter/MkvExporter.cs b/SpineViewer/Exporter/MkvExporter.cs deleted file mode 100644 index 476d4e2..0000000 --- a/SpineViewer/Exporter/MkvExporter.cs +++ /dev/null @@ -1,49 +0,0 @@ -using FFMpegCore; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SpineViewer.Exporter -{ - /// - /// MKV 导出参数 - /// - public class MkvExporter : FFmpegVideoExporter - { - public MkvExporter() - { - BackgroundColor = new(0, 255, 0); - } - - public override string Format => "matroska"; - - public override string Suffix => ".mkv"; - - /// - /// 编码器 - /// - public string Codec { get; set; } = "libx265"; - - /// - /// CRF - /// - public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); } - private int crf = 23; - - /// - /// 像素格式 - /// - public string PixelFormat { get; set; } = "yuv444p"; - - public override string FileNameNoteSuffix => $"{Codec}_{CRF}_{PixelFormat}"; - - public override void SetOutputOptions(FFMpegArgumentOptions options) - { - base.SetOutputOptions(options); - options.WithVideoCodec(Codec).WithConstantRateFactor(CRF).ForcePixelFormat(PixelFormat); - } - } -} diff --git a/SpineViewer/Exporter/MovExporter.cs b/SpineViewer/Exporter/MovExporter.cs deleted file mode 100644 index 958f889..0000000 --- a/SpineViewer/Exporter/MovExporter.cs +++ /dev/null @@ -1,48 +0,0 @@ -using FFMpegCore; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SpineViewer.Exporter -{ - /// - /// MOV 导出参数 - /// - public class MovExporter : FFmpegVideoExporter - { - public MovExporter() - { - BackgroundColor = new(0, 255, 0); - } - - public override string Format => "mov"; - - public override string Suffix => ".mov"; - - /// - /// 编码器 - /// - public string Codec { get; set; } = "prores_ks"; - - /// - /// 预设 - /// - public string Profile { get; set; } = "auto"; - - /// - /// 像素格式 - /// - public string PixelFormat { get; set; } = "yuva444p10le"; - - public override string FileNameNoteSuffix => $"{Codec}_{Profile}_{PixelFormat}"; - - public override void SetOutputOptions(FFMpegArgumentOptions options) - { - base.SetOutputOptions(options); - options.WithFastStart().WithVideoCodec(Codec).WithCustomArgument($"-profile {Profile}").ForcePixelFormat(PixelFormat); - } - } -} diff --git a/SpineViewer/Exporter/Mp4Exporter.cs b/SpineViewer/Exporter/Mp4Exporter.cs deleted file mode 100644 index e554ddc..0000000 --- a/SpineViewer/Exporter/Mp4Exporter.cs +++ /dev/null @@ -1,49 +0,0 @@ -using FFMpegCore; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SpineViewer.Exporter -{ - /// - /// MP4 导出参数 - /// - public class Mp4Exporter : FFmpegVideoExporter - { - public Mp4Exporter() - { - BackgroundColor = new(0, 255, 0); - } - - public override string Format => "mp4"; - - public override string Suffix => ".mp4"; - - /// - /// 编码器 - /// - public string Codec { get; set; } = "libx264"; - - /// - /// CRF - /// - public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); } - private int crf = 23; - - /// - /// 像素格式 - /// - public string PixelFormat { get; set; } = "yuv444p"; - - public override string FileNameNoteSuffix => $"{Codec}_{CRF}_{PixelFormat}"; - - public override void SetOutputOptions(FFMpegArgumentOptions options) - { - base.SetOutputOptions(options); - options.WithFastStart().WithVideoCodec(Codec).WithConstantRateFactor(CRF).ForcePixelFormat(PixelFormat); - } - } -} diff --git a/SpineViewer/Exporter/WebmExporter.cs b/SpineViewer/Exporter/WebmExporter.cs deleted file mode 100644 index b8097fd..0000000 --- a/SpineViewer/Exporter/WebmExporter.cs +++ /dev/null @@ -1,50 +0,0 @@ -using FFMpegCore; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SpineViewer.Exporter -{ - /// - /// WebM 导出参数 - /// - public class WebmExporter : FFmpegVideoExporter - { - public WebmExporter() - { - // 默认用透明黑背景 - BackgroundColor = new(0, 0, 0, 0); - } - - public override string Format => "webm"; - - public override string Suffix => ".webm"; - - /// - /// 编码器 - /// - public string Codec { get; set; } = "libvpx-vp9"; - - /// - /// CRF - /// - public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); } - private int crf = 23; - - /// - /// 像素格式 - /// - public string PixelFormat { get; set; } = "yuva420p"; - - public override string FileNameNoteSuffix => $"{Codec}_{CRF}_{PixelFormat}"; - - public override void SetOutputOptions(FFMpegArgumentOptions options) - { - base.SetOutputOptions(options); - options.WithVideoCodec(Codec).WithConstantRateFactor(CRF).ForcePixelFormat(PixelFormat); - } - } -} diff --git a/SpineViewer/Exporter/WebpExporter.cs b/SpineViewer/Exporter/WebpExporter.cs deleted file mode 100644 index 4fb93ac..0000000 --- a/SpineViewer/Exporter/WebpExporter.cs +++ /dev/null @@ -1,60 +0,0 @@ -using FFMpegCore; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SpineViewer.Exporter -{ - /// - /// MP4 导出参数 - /// - public class WebpExporter : FFmpegVideoExporter - { - public WebpExporter() - { - FPS = 24; - } - - public override string Format => "webp"; - - public override string Suffix => ".webp"; - - /// - /// 编码器 - /// - public string Codec { get; set; } = "libwebp_anim"; - - /// - /// 是否无损 - /// - public bool Lossless { get; set; } = false; - - /// - /// 质量 - /// - public int Quality { get => quality; set => quality = Math.Clamp(value, 0, 100); } - private int quality = 75; - - /// - /// 像素格式 - /// - public string PixelFormat { get; set; } = "yuva420p"; - - /// - /// 循环次数, 0 无限循环, 取值范围 [0, 65535] - /// - public int Loop { get => loop; set => loop = Math.Clamp(value, 0, 65535); } - private int loop = 0; - - public override string FileNameNoteSuffix => $"{Codec}_{Quality}_{PixelFormat}"; - - public override void SetOutputOptions(FFMpegArgumentOptions options) - { - base.SetOutputOptions(options); - options.WithVideoCodec(Codec).ForcePixelFormat(PixelFormat).WithCustomArgument($"-lossless {(Lossless ? 1 : 0)} -quality {Quality} -loop {Loop}"); - } - } -} diff --git a/SpineViewer/Forms/SpineViewerForm.cs b/SpineViewer/Forms/SpineViewerForm.cs index 1ecefe9..9cec6e7 100644 --- a/SpineViewer/Forms/SpineViewerForm.cs +++ b/SpineViewer/Forms/SpineViewerForm.cs @@ -2,11 +2,9 @@ using SpineViewer.Spine; using System.ComponentModel; using System.Diagnostics; -using SpineViewer.Exporter; -using System.Reflection.Metadata; -using SpineViewer.PropertyGridWrappers.Exporter; using SpineViewer.Natives; using SpineViewer.Utils; +using SpineViewer.Spine.SpineExporter; namespace SpineViewer { @@ -14,7 +12,7 @@ namespace SpineViewer { private readonly Logger logger = LogManager.GetCurrentClassLogger(); - private readonly Dictionary exporterCache = []; + private readonly Dictionary exporterCache = []; public SpineViewerForm() { @@ -98,7 +96,7 @@ namespace SpineViewer exporter.View = spinePreviewer.GetView(); exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly; - var exportDialog = new Dialogs.ExportDialog(new FrameExporterWrapper((FrameExporter)exporter)); + var exportDialog = new Dialogs.ExportDialog(new FrameExporterProperty((FrameExporter)exporter)); if (exportDialog.ShowDialog() != DialogResult.OK) return; @@ -118,7 +116,7 @@ namespace SpineViewer exporter.View = spinePreviewer.GetView(); exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly; - var exportDialog = new Dialogs.ExportDialog(new FrameSequenceExporterWrapper((FrameSequenceExporter)exporter)); + var exportDialog = new Dialogs.ExportDialog(new FrameSequenceExporterProperty((FrameSequenceExporter)exporter)); if (exportDialog.ShowDialog() != DialogResult.OK) return; @@ -138,7 +136,7 @@ namespace SpineViewer exporter.View = spinePreviewer.GetView(); exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly; - var exportDialog = new Dialogs.ExportDialog(new GifExporterWrapper((GifExporter)exporter)); + var exportDialog = new Dialogs.ExportDialog(new GifExporterProperty((GifExporter)exporter)); if (exportDialog.ShowDialog() != DialogResult.OK) return; @@ -158,7 +156,7 @@ namespace SpineViewer exporter.View = spinePreviewer.GetView(); exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly; - var exportDialog = new Dialogs.ExportDialog(new WebpExporterWrapper((WebpExporter)exporter)); + var exportDialog = new Dialogs.ExportDialog(new WebpExporterProperty((WebpExporter)exporter)); if (exportDialog.ShowDialog() != DialogResult.OK) return; @@ -178,7 +176,7 @@ namespace SpineViewer exporter.View = spinePreviewer.GetView(); exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly; - var exportDialog = new Dialogs.ExportDialog(new AvifExporterWrapper((AvifExporter)exporter)); + var exportDialog = new Dialogs.ExportDialog(new AvifExporterProperty((AvifExporter)exporter)); if (exportDialog.ShowDialog() != DialogResult.OK) return; @@ -198,7 +196,7 @@ namespace SpineViewer exporter.View = spinePreviewer.GetView(); exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly; - var exportDialog = new Dialogs.ExportDialog(new Mp4ExporterWrapper((Mp4Exporter)exporter)); + var exportDialog = new Dialogs.ExportDialog(new Mp4ExporterProperty((Mp4Exporter)exporter)); if (exportDialog.ShowDialog() != DialogResult.OK) return; @@ -218,7 +216,7 @@ namespace SpineViewer exporter.View = spinePreviewer.GetView(); exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly; - var exportDialog = new Dialogs.ExportDialog(new WebmExporterWrapper((WebmExporter)exporter)); + var exportDialog = new Dialogs.ExportDialog(new WebmExporterProperty((WebmExporter)exporter)); if (exportDialog.ShowDialog() != DialogResult.OK) return; @@ -238,7 +236,7 @@ namespace SpineViewer exporter.View = spinePreviewer.GetView(); exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly; - var exportDialog = new Dialogs.ExportDialog(new MkvExporterWrapper((MkvExporter)exporter)); + var exportDialog = new Dialogs.ExportDialog(new MkvExporterProperty((MkvExporter)exporter)); if (exportDialog.ShowDialog() != DialogResult.OK) return; @@ -258,7 +256,7 @@ namespace SpineViewer exporter.View = spinePreviewer.GetView(); exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly; - var exportDialog = new Dialogs.ExportDialog(new MovExporterWrapper((MovExporter)exporter)); + var exportDialog = new Dialogs.ExportDialog(new MovExporterProperty((MovExporter)exporter)); if (exportDialog.ShowDialog() != DialogResult.OK) return; @@ -278,7 +276,7 @@ namespace SpineViewer exporter.View = spinePreviewer.GetView(); exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly; - var exportDialog = new Dialogs.ExportDialog(new CustomExporterWrapper((CustomExporter)exporter)); + var exportDialog = new Dialogs.ExportDialog(new CustomExporterProperty((CustomExporter)exporter)); if (exportDialog.ShowDialog() != DialogResult.OK) return; @@ -337,7 +335,7 @@ namespace SpineViewer private void Export_Work(object? sender, DoWorkEventArgs e) { var worker = (BackgroundWorker)sender; - var exporter = (Exporter.Exporter)e.Argument; + var exporter = (Exporter)e.Argument; Invoke(() => TaskbarManager.SetProgressState(Handle, TBPFLAG.TBPF_INDETERMINATE)); spinePreviewer.StopRender(); lock (spineListView.Spines) { exporter.Export(spineListView.Spines.Where(sp => !sp.IsHidden).ToArray(), (BackgroundWorker)sender); } diff --git a/SpineViewer/PropertyGridWrappers/Exporter/ExporterWrapper.cs b/SpineViewer/PropertyGridWrappers/Exporter/ExporterWrapper.cs deleted file mode 100644 index a8c1aa8..0000000 --- a/SpineViewer/PropertyGridWrappers/Exporter/ExporterWrapper.cs +++ /dev/null @@ -1,56 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Drawing.Design; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SpineViewer.PropertyGridWrappers.Exporter -{ - public class ExporterWrapper(SpineViewer.Exporter.Exporter exporter) - { - [Browsable(false)] - public virtual SpineViewer.Exporter.Exporter Exporter { get; } = exporter; - - /// - /// 输出文件夹 - /// - [Editor(typeof(FolderNameEditor), typeof(UITypeEditor))] - [Category("[0] 导出"), DisplayName("输出文件夹"), Description("逐个导出时可以留空,将逐个导出到模型自身所在目录")] - public string? OutputDir { get => Exporter.OutputDir; set => Exporter.OutputDir = value; } - - /// - /// 导出单个 - /// - [Category("[0] 导出"), DisplayName("导出单个"), Description("是否将模型在同一个画面上导出单个文件,否则逐个导出模型")] - public bool IsExportSingle { get => Exporter.IsExportSingle; set => Exporter.IsExportSingle = value; } - - /// - /// 画面分辨率 - /// - [TypeConverter(typeof(SizeConverter))] - [Category("[0] 导出"), DisplayName("分辨率"), Description("画面的宽高像素大小,请在预览画面参数面板进行调整")] - public Size Resolution { get => Exporter.Resolution; } - - /// - /// 渲染视窗 - /// - [Category("[0] 导出"), DisplayName("视图"), Description("画面的视图参数,请在预览画面参数面板进行调整")] - public SFML.Graphics.View View { get => Exporter.View; } - - /// - /// 是否仅渲染选中 - /// - [Category("[0] 导出"), DisplayName("仅渲染选中"), Description("是否仅导出选中的模型,请在预览画面参数面板进行调整")] - public bool RenderSelectedOnly { get => Exporter.RenderSelectedOnly; } - - /// - /// 背景颜色 - /// - [Editor(typeof(SFMLColorEditor), typeof(UITypeEditor))] - [TypeConverter(typeof(SFMLColorConverter))] - [Category("[0] 导出"), DisplayName("背景颜色"), Description("要使用的背景色, 格式为 #RRGGBBAA")] - public SFML.Graphics.Color BackgroundColor { get => Exporter.BackgroundColor; set => Exporter.BackgroundColor = value; } - } -} diff --git a/SpineViewer/PropertyGridWrappers/Exporter/FFmpegVideoExporterWrapper.cs b/SpineViewer/PropertyGridWrappers/Exporter/FFmpegVideoExporterWrapper.cs deleted file mode 100644 index b8e268a..0000000 --- a/SpineViewer/PropertyGridWrappers/Exporter/FFmpegVideoExporterWrapper.cs +++ /dev/null @@ -1,34 +0,0 @@ -using SpineViewer.Exporter; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SpineViewer.PropertyGridWrappers.Exporter -{ - public class FFmpegVideoExporterWrapper(FFmpegVideoExporter exporter) : VideoExporterWrapper(exporter) - { - [Browsable(false)] - public override FFmpegVideoExporter Exporter => (FFmpegVideoExporter)base.Exporter; - - /// - /// 文件格式 - /// - [Category("[2] FFmpeg 基本参数"), DisplayName("文件格式"), Description("-f, 文件格式")] - public virtual string Format => Exporter.Format; - - /// - /// 文件名后缀 - /// - [Category("[2] FFmpeg 基本参数"), DisplayName("文件名后缀"), Description("文件名后缀")] - public virtual string Suffix => Exporter.Suffix; - - /// - /// 文件名后缀 - /// - [Category("[2] FFmpeg 基本参数"), DisplayName("自定义参数"), Description("使用 \"ffmpeg -h encoder=<编码器>\" 查看编码器支持的参数\n使用 \"ffmpeg -h muxer=<文件格式>\" 查看文件格式支持的参数")] - public string CustomArgument { get => Exporter.CustomArgument; set => Exporter.CustomArgument = value; } - } -} diff --git a/SpineViewer/PropertyGridWrappers/Exporter/FrameExporterWrapper.cs b/SpineViewer/PropertyGridWrappers/Exporter/FrameExporterWrapper.cs deleted file mode 100644 index ddf522d..0000000 --- a/SpineViewer/PropertyGridWrappers/Exporter/FrameExporterWrapper.cs +++ /dev/null @@ -1,37 +0,0 @@ -using SpineViewer.Exporter; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Drawing.Imaging; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SpineViewer.PropertyGridWrappers.Exporter -{ - public class FrameExporterWrapper(FrameExporter exporter) : ExporterWrapper(exporter) - { - [Browsable(false)] - public override FrameExporter Exporter => (FrameExporter)base.Exporter; - - /// - /// 单帧画面格式 - /// - [TypeConverter(typeof(ImageFormatConverter))] - [Category("[1] 单帧画面"), DisplayName("图像格式")] - public ImageFormat ImageFormat { get => Exporter.ImageFormat; set => Exporter.ImageFormat = value; } - - /// - /// 文件名后缀 - /// - [Category("[1] 单帧画面"), DisplayName("文件名后缀"), Description("与图像格式匹配的文件名后缀")] - public string Suffix { get => Exporter.ImageFormat.GetSuffix(); } - - /// - /// DPI - /// - [TypeConverter(typeof(SizeFConverter))] - [Category("[1] 单帧画面"), DisplayName("DPI"), Description("导出图像的每英寸像素数,用于调整图像的物理尺寸")] - public SizeF DPI { get => Exporter.DPI; set => Exporter.DPI = value; } - } -} diff --git a/SpineViewer/PropertyGridWrappers/Exporter/FrameSequenceExporterWrapper.cs b/SpineViewer/PropertyGridWrappers/Exporter/FrameSequenceExporterWrapper.cs deleted file mode 100644 index 5e05570..0000000 --- a/SpineViewer/PropertyGridWrappers/Exporter/FrameSequenceExporterWrapper.cs +++ /dev/null @@ -1,23 +0,0 @@ -using SpineViewer.Exporter; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SpineViewer.PropertyGridWrappers.Exporter -{ - public class FrameSequenceExporterWrapper(VideoExporter exporter) : VideoExporterWrapper(exporter) - { - [Browsable(false)] - public override FrameSequenceExporter Exporter => (FrameSequenceExporter)base.Exporter; - - /// - /// 文件名后缀 - /// - [TypeConverter(typeof(StringEnumConverter)), StringEnumConverter.StandardValues(".png", ".jpg", ".tga", ".bmp")] - [Category("[2] 帧序列参数"), DisplayName("文件名后缀"), Description("帧文件的后缀,同时决定帧图像格式")] - public string Suffix { get => Exporter.Suffix; set => Exporter.Suffix = value; } - } -} diff --git a/SpineViewer/PropertyGridWrappers/Exporter/GifExporterWrapper.cs b/SpineViewer/PropertyGridWrappers/Exporter/GifExporterWrapper.cs deleted file mode 100644 index fa91755..0000000 --- a/SpineViewer/PropertyGridWrappers/Exporter/GifExporterWrapper.cs +++ /dev/null @@ -1,34 +0,0 @@ -using SpineViewer.Exporter; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SpineViewer.PropertyGridWrappers.Exporter -{ - class GifExporterWrapper(FFmpegVideoExporter exporter) : FFmpegVideoExporterWrapper(exporter) - { - [Browsable(false)] - public override GifExporter Exporter => (GifExporter)base.Exporter; - - /// - /// 调色板最大颜色数量 - /// - [Category("[3] 格式参数"), DisplayName("调色板最大颜色数量"), Description("设置调色板使用的最大颜色数量, 越多则色彩保留程度越高")] - public uint MaxColors { get => Exporter.MaxColors; set => Exporter.MaxColors = value; } - - /// - /// 透明度阈值 - /// - [Category("[3] 格式参数"), DisplayName("透明度阈值"), Description("小于该值的像素点会被认为是透明像素")] - public byte AlphaThreshold { get => Exporter.AlphaThreshold; set => Exporter.AlphaThreshold = value; } - - /// - /// 透明度阈值 - /// - [Category("[3] 格式参数"), DisplayName("循环次数"), Description("-loop, 循环次数, -1 不循环, 0 无限循环, 取值范围 [-1, 65535]")] - public int Loop { get => Exporter.Loop; set => Exporter.Loop = value; } - } -} diff --git a/SpineViewer/PropertyGridWrappers/Exporter/VideoExporterWrapper.cs b/SpineViewer/PropertyGridWrappers/Exporter/VideoExporterWrapper.cs deleted file mode 100644 index 3becc3e..0000000 --- a/SpineViewer/PropertyGridWrappers/Exporter/VideoExporterWrapper.cs +++ /dev/null @@ -1,34 +0,0 @@ -using SpineViewer.Exporter; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SpineViewer.PropertyGridWrappers.Exporter -{ - public class VideoExporterWrapper(VideoExporter exporter) : ExporterWrapper(exporter) - { - [Browsable(false)] - public override VideoExporter Exporter => (VideoExporter)base.Exporter; - - /// - /// 导出时长 - /// - [Category("[1] 视频参数"), DisplayName("时长"), Description("可以从模型列表查看动画时长, 如果小于 0, 则在逐个导出时每个模型使用各自的所有轨道动画时长最大值")] - public float Duration { get => Exporter.Duration; set => Exporter.Duration = value; } - - /// - /// 帧率 - /// - [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; } - } -} diff --git a/SpineViewer/PropertyGridWrappers/Spine/SpineAnimationWrapper.cs b/SpineViewer/PropertyGridWrappers/Spine/SpineAnimationWrapper.cs index 2264c59..19bc04d 100644 --- a/SpineViewer/PropertyGridWrappers/Spine/SpineAnimationWrapper.cs +++ b/SpineViewer/PropertyGridWrappers/Spine/SpineAnimationWrapper.cs @@ -1,4 +1,5 @@ using SpineViewer.Spine; +using SpineViewer.Utils; using System; using System.Collections.Generic; using System.ComponentModel; diff --git a/SpineViewer/PropertyGridWrappers/Spine/SpineBaseInfoWrapper.cs b/SpineViewer/PropertyGridWrappers/Spine/SpineBaseInfoWrapper.cs index 8741051..d10ef20 100644 --- a/SpineViewer/PropertyGridWrappers/Spine/SpineBaseInfoWrapper.cs +++ b/SpineViewer/PropertyGridWrappers/Spine/SpineBaseInfoWrapper.cs @@ -1,4 +1,5 @@ using SpineViewer.Spine; +using SpineViewer.Utils; using System; using System.Collections.Generic; using System.ComponentModel; diff --git a/SpineViewer/PropertyGridWrappers/Spine/SpineSkinWrapper.cs b/SpineViewer/PropertyGridWrappers/Spine/SpineSkinWrapper.cs index 2932db5..c0da519 100644 --- a/SpineViewer/PropertyGridWrappers/Spine/SpineSkinWrapper.cs +++ b/SpineViewer/PropertyGridWrappers/Spine/SpineSkinWrapper.cs @@ -1,4 +1,5 @@ using SpineViewer.Spine; +using SpineViewer.Utils; using System; using System.Collections.Generic; using System.ComponentModel; diff --git a/SpineViewer/PropertyGridWrappers/Spine/SpineTransformWrapper.cs b/SpineViewer/PropertyGridWrappers/Spine/SpineTransformWrapper.cs index febf1f6..8930984 100644 --- a/SpineViewer/PropertyGridWrappers/Spine/SpineTransformWrapper.cs +++ b/SpineViewer/PropertyGridWrappers/Spine/SpineTransformWrapper.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using SpineViewer.Spine; +using SpineViewer.Utils; namespace SpineViewer.PropertyGridWrappers.Spine { diff --git a/SpineViewer/PropertyGridWrappers/SpinePreviewerWrapper.cs b/SpineViewer/PropertyGridWrappers/SpinePreviewerWrapper.cs deleted file mode 100644 index a833123..0000000 --- a/SpineViewer/PropertyGridWrappers/SpinePreviewerWrapper.cs +++ /dev/null @@ -1,48 +0,0 @@ -using SpineViewer.Controls; -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace SpineViewer.PropertyGridWrappers -{ - /// - /// 用于在 PropertyGrid 上显示 SpinePreviewe 属性的包装类 - /// - public class SpinePreviewerWrapper(SpinePreviewer previewer) - { - [Browsable(false)] - public SpinePreviewer Previewer { get; } = previewer; - - [TypeConverter(typeof(SizeConverter))] - [Category("[0] 导出"), DisplayName("分辨率")] - public Size Resolution { get => Previewer.Resolution; set => Previewer.Resolution = value; } - - [TypeConverter(typeof(PointFConverter))] - [Category("[0] 导出"), DisplayName("画面中心点")] - public PointF Center { get => Previewer.Center; set => Previewer.Center = value; } - - [Category("[0] 导出"), DisplayName("缩放")] - public float Zoom { get => Previewer.Zoom; set => Previewer.Zoom = value; } - - [Category("[0] 导出"), DisplayName("旋转")] - public float Rotation { get => Previewer.Rotation; set => Previewer.Rotation = value; } - - [Category("[0] 导出"), DisplayName("水平翻转")] - public bool FlipX { get => Previewer.FlipX; set => Previewer.FlipX = value; } - - [Category("[0] 导出"), DisplayName("垂直翻转")] - public bool FlipY { get => Previewer.FlipY; set => Previewer.FlipY = value; } - - [Category("[0] 导出"), DisplayName("仅渲染选中")] - public bool RenderSelectedOnly { get => Previewer.RenderSelectedOnly; set => Previewer.RenderSelectedOnly = value; } - - [Category("[1] 预览"), DisplayName("显示坐标轴")] - public bool ShowAxis { get => Previewer.ShowAxis; set => Previewer.ShowAxis = value; } - - [Category("[1] 预览"), DisplayName("最大帧率")] - public uint MaxFps { get => Previewer.MaxFps; set => Previewer.MaxFps = value; } - } -} diff --git a/SpineViewer/PropertyGridWrappers/Exporter/AvifExporterWrapper.cs b/SpineViewer/Spine/SpineExporter/AvifExporter.cs similarity index 54% rename from SpineViewer/PropertyGridWrappers/Exporter/AvifExporterWrapper.cs rename to SpineViewer/Spine/SpineExporter/AvifExporter.cs index 4f5722f..a06ed17 100644 --- a/SpineViewer/PropertyGridWrappers/Exporter/AvifExporterWrapper.cs +++ b/SpineViewer/Spine/SpineExporter/AvifExporter.cs @@ -1,4 +1,5 @@ -using SpineViewer.Exporter; +using FFMpegCore; +using SpineViewer.Utils; using System; using System.Collections.Generic; using System.ComponentModel; @@ -6,9 +7,54 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace SpineViewer.PropertyGridWrappers.Exporter +namespace SpineViewer.Spine.SpineExporter { - public class AvifExporterWrapper(AvifExporter exporter) : FFmpegVideoExporterWrapper(exporter) + /// + /// MP4 导出参数 + /// + public class AvifExporter : FFmpegVideoExporter + { + public AvifExporter() + { + FPS = 24; + } + + public override string Format => "avif"; + + public override string Suffix => ".avif"; + + /// + /// 编码器 + /// + public string Codec { get; set; } = "av1_nvenc"; + + /// + /// CRF + /// + public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); } + private int crf = 23; + + /// + /// 像素格式 + /// + public string PixelFormat { get; set; } = "yuv420p"; + + /// + /// 循环次数, 0 无限循环, 取值范围 [0, 65535] + /// + public int Loop { get => loop; set => loop = Math.Clamp(value, 0, 65535); } + private int loop = 0; + + public override string FileNameNoteSuffix => $"{Codec}_{CRF}_{PixelFormat}"; + + public override void SetOutputOptions(FFMpegArgumentOptions options) + { + base.SetOutputOptions(options); + options.WithVideoCodec(Codec).ForcePixelFormat(PixelFormat).WithConstantRateFactor(CRF).WithCustomArgument($"-loop {Loop}"); + } + } + + public class AvifExporterProperty(AvifExporter exporter) : FFmpegVideoExporterProperty(exporter) { [Browsable(false)] public override AvifExporter Exporter => (AvifExporter)base.Exporter; diff --git a/SpineViewer/PropertyGridWrappers/Exporter/CustomExporterWrapper.cs b/SpineViewer/Spine/SpineExporter/CustomExporter.cs similarity index 52% rename from SpineViewer/PropertyGridWrappers/Exporter/CustomExporterWrapper.cs rename to SpineViewer/Spine/SpineExporter/CustomExporter.cs index b820186..f7c8e5c 100644 --- a/SpineViewer/PropertyGridWrappers/Exporter/CustomExporterWrapper.cs +++ b/SpineViewer/Spine/SpineExporter/CustomExporter.cs @@ -1,14 +1,40 @@ -using SpineViewer.Exporter; -using System; +using System; using System.Collections.Generic; using System.ComponentModel; using System.Linq; using System.Text; using System.Threading.Tasks; -namespace SpineViewer.PropertyGridWrappers.Exporter +namespace SpineViewer.Spine.SpineExporter { - public class CustomExporterWrapper(CustomExporter exporter) : FFmpegVideoExporterWrapper(exporter) + /// + /// FFmpeg 自定义视频导出参数 + /// + public class CustomExporter : FFmpegVideoExporter + { + public CustomExporter() + { + CustomArgument = "-c:v libx264 -crf 23 -pix_fmt yuv420p"; // 提供一个示例参数 + } + + public override string Format => CustomFormat; + + public override string Suffix => CustomSuffix; + + public override string FileNameNoteSuffix => string.Empty; + + /// + /// 文件格式 + /// + public string CustomFormat { get; set; } = "mp4"; + + /// + /// 文件名后缀 + /// + public string CustomSuffix { get; set; } = ".mp4"; + } + + public class CustomExporterProperty(CustomExporter exporter) : FFmpegVideoExporterProperty(exporter) { [Browsable(false)] public override CustomExporter Exporter => (CustomExporter)base.Exporter; diff --git a/SpineViewer/Exporter/ExportHelper.cs b/SpineViewer/Spine/SpineExporter/ExportHelper.cs similarity index 98% rename from SpineViewer/Exporter/ExportHelper.cs rename to SpineViewer/Spine/SpineExporter/ExportHelper.cs index c864479..f1310f6 100644 --- a/SpineViewer/Exporter/ExportHelper.cs +++ b/SpineViewer/Spine/SpineExporter/ExportHelper.cs @@ -6,7 +6,7 @@ using System.Drawing.Imaging; using System.Linq; using System.Text; -namespace SpineViewer.Exporter +namespace SpineViewer.Spine.SpineExporter { /// /// SFML.Graphics.Image 帧对象包装类, 将接管给定的 image 对象生命周期 diff --git a/SpineViewer/Exporter/Exporter.cs b/SpineViewer/Spine/SpineExporter/Exporter.cs similarity index 70% rename from SpineViewer/Exporter/Exporter.cs rename to SpineViewer/Spine/SpineExporter/Exporter.cs index 35c68ab..b17b178 100644 --- a/SpineViewer/Exporter/Exporter.cs +++ b/SpineViewer/Spine/SpineExporter/Exporter.cs @@ -1,6 +1,5 @@ using NLog; using SpineViewer.Extensions; -using SpineViewer.PropertyGridWrappers; using SpineViewer.Utils; using System; using System.Collections.Generic; @@ -11,7 +10,7 @@ using System.Reflection; using System.Text; using System.Threading.Tasks; -namespace SpineViewer.Exporter +namespace SpineViewer.Spine.SpineExporter { /// /// 导出器基类 @@ -96,12 +95,12 @@ namespace SpineViewer.Exporter /// /// 获取单个模型的单帧画面 /// - protected SFMLImageVideoFrame GetFrame(Spine.SpineObject spine) => GetFrame([spine]); + protected SFMLImageVideoFrame GetFrame(SpineObject spine) => GetFrame([spine]); /// /// 获取模型列表的单帧画面 /// - protected SFMLImageVideoFrame GetFrame(Spine.SpineObject[] spinesToRender) + protected SFMLImageVideoFrame GetFrame(SpineObject[] spinesToRender) { // RenderTexture 必须临时创建, 随用随取, 防止出现跨线程的情况 using var texPma = GetRenderTexture(); @@ -149,12 +148,12 @@ namespace SpineViewer.Exporter /// /// 每个模型在同一个画面进行导出 /// - protected abstract void ExportSingle(Spine.SpineObject[] spinesToRender, BackgroundWorker? worker = null); + protected abstract void ExportSingle(SpineObject[] spinesToRender, BackgroundWorker? worker = null); /// /// 每个模型独立导出 /// - protected abstract void ExportIndividual(Spine.SpineObject[] spinesToRender, BackgroundWorker? worker = null); + protected abstract void ExportIndividual(SpineObject[] spinesToRender, BackgroundWorker? worker = null); /// /// 检查参数是否合法并规范化参数值, 否则返回用户错误原因 @@ -178,7 +177,7 @@ namespace SpineViewer.Exporter /// 要进行导出的 Spine 列表 /// 用来执行该函数的 worker /// - public virtual void Export(Spine.SpineObject[] spines, BackgroundWorker? worker = null) + public virtual void Export(SpineObject[] spines, BackgroundWorker? worker = null) { if (Validate() is string err) throw new ArgumentException(err); @@ -191,4 +190,53 @@ namespace SpineViewer.Exporter logger.LogCurrentProcessMemoryUsage(); } } + + /// + /// 用于在 PropertyGrid 上提供用户操作接口的包装类 + /// + public class ExporterProperty(Exporter exporter) + { + [Browsable(false)] + public virtual Exporter Exporter { get; } = exporter; + + /// + /// 输出文件夹 + /// + [Editor(typeof(FolderNameEditor), typeof(UITypeEditor))] + [Category("[0] 导出"), DisplayName("输出文件夹"), Description("逐个导出时可以留空,将逐个导出到模型自身所在目录")] + public string? OutputDir { get => Exporter.OutputDir; set => Exporter.OutputDir = value; } + + /// + /// 导出单个 + /// + [Category("[0] 导出"), DisplayName("导出单个"), Description("是否将模型在同一个画面上导出单个文件,否则逐个导出模型")] + public bool IsExportSingle { get => Exporter.IsExportSingle; set => Exporter.IsExportSingle = value; } + + /// + /// 画面分辨率 + /// + [TypeConverter(typeof(SizeConverter))] + [Category("[0] 导出"), DisplayName("分辨率"), Description("画面的宽高像素大小,请在预览画面参数面板进行调整")] + public Size Resolution { get => Exporter.Resolution; } + + /// + /// 渲染视窗 + /// + [Category("[0] 导出"), DisplayName("视图"), Description("画面的视图参数,请在预览画面参数面板进行调整")] + public SFML.Graphics.View View { get => Exporter.View; } + + /// + /// 是否仅渲染选中 + /// + [Category("[0] 导出"), DisplayName("仅渲染选中"), Description("是否仅导出选中的模型,请在预览画面参数面板进行调整")] + public bool RenderSelectedOnly { get => Exporter.RenderSelectedOnly; } + + /// + /// 背景颜色 + /// + [Editor(typeof(SFMLColorEditor), typeof(UITypeEditor))] + [TypeConverter(typeof(SFMLColorConverter))] + [Category("[0] 导出"), DisplayName("背景颜色"), Description("要使用的背景色, 格式为 #RRGGBBAA")] + public SFML.Graphics.Color BackgroundColor { get => Exporter.BackgroundColor; set => Exporter.BackgroundColor = value; } + } } diff --git a/SpineViewer/Exporter/FFmpegVideoExporter.cs b/SpineViewer/Spine/SpineExporter/FFmpegVideoExporter.cs similarity index 73% rename from SpineViewer/Exporter/FFmpegVideoExporter.cs rename to SpineViewer/Spine/SpineExporter/FFmpegVideoExporter.cs index 9490d19..e7147ce 100644 --- a/SpineViewer/Exporter/FFmpegVideoExporter.cs +++ b/SpineViewer/Spine/SpineExporter/FFmpegVideoExporter.cs @@ -8,7 +8,7 @@ using System.Text; using System.Threading.Tasks; using System.Diagnostics; -namespace SpineViewer.Exporter +namespace SpineViewer.Spine.SpineExporter { /// /// 使用 FFmpeg 的视频导出器 @@ -51,7 +51,7 @@ namespace SpineViewer.Exporter return null; } - protected override void ExportSingle(Spine.SpineObject[] spinesToRender, BackgroundWorker? worker = null) + protected override void ExportSingle(SpineObject[] spinesToRender, BackgroundWorker? worker = null) { var noteSuffix = FileNameNoteSuffix; if (!string.IsNullOrWhiteSpace(noteSuffix)) noteSuffix = $"_{noteSuffix}"; @@ -76,7 +76,7 @@ namespace SpineViewer.Exporter } } - protected override void ExportIndividual(Spine.SpineObject[] spinesToRender, BackgroundWorker? worker = null) + protected override void ExportIndividual(SpineObject[] spinesToRender, BackgroundWorker? worker = null) { var noteSuffix = FileNameNoteSuffix; if (!string.IsNullOrWhiteSpace(noteSuffix)) noteSuffix = $"_{noteSuffix}"; @@ -108,4 +108,28 @@ namespace SpineViewer.Exporter } } } + + public class FFmpegVideoExporterProperty(FFmpegVideoExporter exporter) : VideoExporterProperty(exporter) + { + [Browsable(false)] + public override FFmpegVideoExporter Exporter => (FFmpegVideoExporter)base.Exporter; + + /// + /// 文件格式 + /// + [Category("[2] FFmpeg 基本参数"), DisplayName("文件格式"), Description("-f, 文件格式")] + public virtual string Format => Exporter.Format; + + /// + /// 文件名后缀 + /// + [Category("[2] FFmpeg 基本参数"), DisplayName("文件名后缀"), Description("文件名后缀")] + public virtual string Suffix => Exporter.Suffix; + + /// + /// 文件名后缀 + /// + [Category("[2] FFmpeg 基本参数"), DisplayName("自定义参数"), Description("使用 \"ffmpeg -h encoder=<编码器>\" 查看编码器支持的参数\n使用 \"ffmpeg -h muxer=<文件格式>\" 查看文件格式支持的参数")] + public string CustomArgument { get => Exporter.CustomArgument; set => Exporter.CustomArgument = value; } + } } diff --git a/SpineViewer/Exporter/FrameExporter.cs b/SpineViewer/Spine/SpineExporter/FrameExporter.cs similarity index 70% rename from SpineViewer/Exporter/FrameExporter.cs rename to SpineViewer/Spine/SpineExporter/FrameExporter.cs index f23df3a..0d7152b 100644 --- a/SpineViewer/Exporter/FrameExporter.cs +++ b/SpineViewer/Spine/SpineExporter/FrameExporter.cs @@ -7,7 +7,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace SpineViewer.Exporter +namespace SpineViewer.Spine.SpineExporter { /// /// 单帧画面导出器 @@ -43,7 +43,7 @@ namespace SpineViewer.Exporter } private SizeF dpi = new(144, 144); - protected override void ExportSingle(Spine.SpineObject[] spinesToRender, BackgroundWorker? worker = null) + protected override void ExportSingle(SpineObject[] spinesToRender, BackgroundWorker? worker = null) { // 导出单个时必定提供输出文件夹 var filename = $"frame_{timestamp}{ImageFormat.GetSuffix()}"; @@ -65,7 +65,7 @@ namespace SpineViewer.Exporter worker?.ReportProgress(100, $"已处理 1/1"); } - protected override void ExportIndividual(Spine.SpineObject[] spinesToRender, BackgroundWorker? worker = null) + protected override void ExportIndividual(SpineObject[] spinesToRender, BackgroundWorker? worker = null) { int total = spinesToRender.Length; int success = 0; @@ -104,4 +104,30 @@ namespace SpineViewer.Exporter logger.Info("{} frames saved successfully", success); } } + + public class FrameExporterProperty(FrameExporter exporter) : ExporterProperty(exporter) + { + [Browsable(false)] + public override FrameExporter Exporter => (FrameExporter)base.Exporter; + + /// + /// 单帧画面格式 + /// + [TypeConverter(typeof(ImageFormatConverter))] + [Category("[1] 单帧画面"), DisplayName("图像格式")] + public ImageFormat ImageFormat { get => Exporter.ImageFormat; set => Exporter.ImageFormat = value; } + + /// + /// 文件名后缀 + /// + [Category("[1] 单帧画面"), DisplayName("文件名后缀"), Description("与图像格式匹配的文件名后缀")] + public string Suffix { get => Exporter.ImageFormat.GetSuffix(); } + + /// + /// DPI + /// + [TypeConverter(typeof(SizeFConverter))] + [Category("[1] 单帧画面"), DisplayName("DPI"), Description("导出图像的每英寸像素数,用于调整图像的物理尺寸")] + public SizeF DPI { get => Exporter.DPI; set => Exporter.DPI = value; } + } } diff --git a/SpineViewer/Exporter/FrameSequenceExporter.cs b/SpineViewer/Spine/SpineExporter/FrameSequenceExporter.cs similarity index 74% rename from SpineViewer/Exporter/FrameSequenceExporter.cs rename to SpineViewer/Spine/SpineExporter/FrameSequenceExporter.cs index 4bbc55f..ac39fcb 100644 --- a/SpineViewer/Exporter/FrameSequenceExporter.cs +++ b/SpineViewer/Spine/SpineExporter/FrameSequenceExporter.cs @@ -1,4 +1,5 @@ using SpineViewer.Spine; +using SpineViewer.Utils; using System; using System.Collections.Generic; using System.ComponentModel; @@ -6,7 +7,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace SpineViewer.Exporter +namespace SpineViewer.Spine.SpineExporter { /// /// 帧序列导出器 @@ -18,7 +19,7 @@ namespace SpineViewer.Exporter /// public string Suffix { get; set; } = ".png"; - protected override void ExportSingle(Spine.SpineObject[] spinesToRender, BackgroundWorker? worker = null) + protected override void ExportSingle(SpineObject[] spinesToRender, BackgroundWorker? worker = null) { // 导出单个时必定提供输出文件夹, var saveDir = Path.Combine(OutputDir, $"frames_{timestamp}_{FPS:f0}"); @@ -47,7 +48,7 @@ namespace SpineViewer.Exporter } } - protected override void ExportIndividual(Spine.SpineObject[] spinesToRender, BackgroundWorker? worker = null) + protected override void ExportIndividual(SpineObject[] spinesToRender, BackgroundWorker? worker = null) { foreach (var spine in spinesToRender) { @@ -82,4 +83,17 @@ namespace SpineViewer.Exporter } } } + + public class FrameSequenceExporterProperty(VideoExporter exporter) : VideoExporterProperty(exporter) + { + [Browsable(false)] + public override FrameSequenceExporter Exporter => (FrameSequenceExporter)base.Exporter; + + /// + /// 文件名后缀 + /// + [TypeConverter(typeof(StringEnumConverter)), StringEnumConverter.StandardValues(".png", ".jpg", ".tga", ".bmp")] + [Category("[2] 帧序列参数"), DisplayName("文件名后缀"), Description("帧文件的后缀,同时决定帧图像格式")] + public string Suffix { get => Exporter.Suffix; set => Exporter.Suffix = value; } + } } diff --git a/SpineViewer/Exporter/GifExporter.cs b/SpineViewer/Spine/SpineExporter/GifExporter.cs similarity index 57% rename from SpineViewer/Exporter/GifExporter.cs rename to SpineViewer/Spine/SpineExporter/GifExporter.cs index 28b2a9f..8395f73 100644 --- a/SpineViewer/Exporter/GifExporter.cs +++ b/SpineViewer/Spine/SpineExporter/GifExporter.cs @@ -6,7 +6,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace SpineViewer.Exporter +namespace SpineViewer.Spine.SpineExporter { /// /// GIF 导出参数 @@ -52,4 +52,28 @@ namespace SpineViewer.Exporter options.WithCustomArgument(customArgs); } } + + class GifExporterProperty(FFmpegVideoExporter exporter) : FFmpegVideoExporterProperty(exporter) + { + [Browsable(false)] + public override GifExporter Exporter => (GifExporter)base.Exporter; + + /// + /// 调色板最大颜色数量 + /// + [Category("[3] 格式参数"), DisplayName("调色板最大颜色数量"), Description("设置调色板使用的最大颜色数量, 越多则色彩保留程度越高")] + public uint MaxColors { get => Exporter.MaxColors; set => Exporter.MaxColors = value; } + + /// + /// 透明度阈值 + /// + [Category("[3] 格式参数"), DisplayName("透明度阈值"), Description("小于该值的像素点会被认为是透明像素")] + public byte AlphaThreshold { get => Exporter.AlphaThreshold; set => Exporter.AlphaThreshold = value; } + + /// + /// 透明度阈值 + /// + [Category("[3] 格式参数"), DisplayName("循环次数"), Description("-loop, 循环次数, -1 不循环, 0 无限循环, 取值范围 [-1, 65535]")] + public int Loop { get => Exporter.Loop; set => Exporter.Loop = value; } + } } diff --git a/SpineViewer/PropertyGridWrappers/Exporter/MkvExporterWrapper.cs b/SpineViewer/Spine/SpineExporter/MkvExporter.cs similarity index 53% rename from SpineViewer/PropertyGridWrappers/Exporter/MkvExporterWrapper.cs rename to SpineViewer/Spine/SpineExporter/MkvExporter.cs index 2c00492..c7be9c2 100644 --- a/SpineViewer/PropertyGridWrappers/Exporter/MkvExporterWrapper.cs +++ b/SpineViewer/Spine/SpineExporter/MkvExporter.cs @@ -1,4 +1,5 @@ -using SpineViewer.Exporter; +using FFMpegCore; +using SpineViewer.Utils; using System; using System.Collections.Generic; using System.ComponentModel; @@ -6,9 +7,48 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace SpineViewer.PropertyGridWrappers.Exporter +namespace SpineViewer.Spine.SpineExporter { - public class MkvExporterWrapper(FFmpegVideoExporter exporter) : FFmpegVideoExporterWrapper(exporter) + /// + /// MKV 导出参数 + /// + public class MkvExporter : FFmpegVideoExporter + { + public MkvExporter() + { + BackgroundColor = new(0, 255, 0); + } + + public override string Format => "matroska"; + + public override string Suffix => ".mkv"; + + /// + /// 编码器 + /// + public string Codec { get; set; } = "libx265"; + + /// + /// CRF + /// + public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); } + private int crf = 23; + + /// + /// 像素格式 + /// + public string PixelFormat { get; set; } = "yuv444p"; + + public override string FileNameNoteSuffix => $"{Codec}_{CRF}_{PixelFormat}"; + + public override void SetOutputOptions(FFMpegArgumentOptions options) + { + base.SetOutputOptions(options); + options.WithVideoCodec(Codec).WithConstantRateFactor(CRF).ForcePixelFormat(PixelFormat); + } + } + + public class MkvExporterProperty(FFmpegVideoExporter exporter) : FFmpegVideoExporterProperty(exporter) { [Browsable(false)] public override MkvExporter Exporter => (MkvExporter)base.Exporter; diff --git a/SpineViewer/PropertyGridWrappers/Exporter/MovExporterWrapper.cs b/SpineViewer/Spine/SpineExporter/MovExporter.cs similarity index 54% rename from SpineViewer/PropertyGridWrappers/Exporter/MovExporterWrapper.cs rename to SpineViewer/Spine/SpineExporter/MovExporter.cs index 7bf3b68..dddcbb6 100644 --- a/SpineViewer/PropertyGridWrappers/Exporter/MovExporterWrapper.cs +++ b/SpineViewer/Spine/SpineExporter/MovExporter.cs @@ -1,4 +1,5 @@ -using SpineViewer.Exporter; +using FFMpegCore; +using SpineViewer.Utils; using System; using System.Collections.Generic; using System.ComponentModel; @@ -6,9 +7,47 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace SpineViewer.PropertyGridWrappers.Exporter +namespace SpineViewer.Spine.SpineExporter { - public class MovExporterWrapper(FFmpegVideoExporter exporter) : FFmpegVideoExporterWrapper(exporter) + /// + /// MOV 导出参数 + /// + public class MovExporter : FFmpegVideoExporter + { + public MovExporter() + { + BackgroundColor = new(0, 255, 0); + } + + public override string Format => "mov"; + + public override string Suffix => ".mov"; + + /// + /// 编码器 + /// + public string Codec { get; set; } = "prores_ks"; + + /// + /// 预设 + /// + public string Profile { get; set; } = "auto"; + + /// + /// 像素格式 + /// + public string PixelFormat { get; set; } = "yuva444p10le"; + + public override string FileNameNoteSuffix => $"{Codec}_{Profile}_{PixelFormat}"; + + public override void SetOutputOptions(FFMpegArgumentOptions options) + { + base.SetOutputOptions(options); + options.WithFastStart().WithVideoCodec(Codec).WithCustomArgument($"-profile {Profile}").ForcePixelFormat(PixelFormat); + } + } + + public class MovExporterProperty(FFmpegVideoExporter exporter) : FFmpegVideoExporterProperty(exporter) { [Browsable(false)] public override MovExporter Exporter => (MovExporter)base.Exporter; diff --git a/SpineViewer/PropertyGridWrappers/Exporter/Mp4ExporterWrapper.cs b/SpineViewer/Spine/SpineExporter/Mp4Exporter.cs similarity index 52% rename from SpineViewer/PropertyGridWrappers/Exporter/Mp4ExporterWrapper.cs rename to SpineViewer/Spine/SpineExporter/Mp4Exporter.cs index be0529d..e6aac2a 100644 --- a/SpineViewer/PropertyGridWrappers/Exporter/Mp4ExporterWrapper.cs +++ b/SpineViewer/Spine/SpineExporter/Mp4Exporter.cs @@ -1,4 +1,5 @@ -using SpineViewer.Exporter; +using FFMpegCore; +using SpineViewer.Utils; using System; using System.Collections.Generic; using System.ComponentModel; @@ -6,9 +7,48 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace SpineViewer.PropertyGridWrappers.Exporter +namespace SpineViewer.Spine.SpineExporter { - public class Mp4ExporterWrapper(FFmpegVideoExporter exporter) : FFmpegVideoExporterWrapper(exporter) + /// + /// MP4 导出参数 + /// + public class Mp4Exporter : FFmpegVideoExporter + { + public Mp4Exporter() + { + BackgroundColor = new(0, 255, 0); + } + + public override string Format => "mp4"; + + public override string Suffix => ".mp4"; + + /// + /// 编码器 + /// + public string Codec { get; set; } = "libx264"; + + /// + /// CRF + /// + public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); } + private int crf = 23; + + /// + /// 像素格式 + /// + public string PixelFormat { get; set; } = "yuv444p"; + + public override string FileNameNoteSuffix => $"{Codec}_{CRF}_{PixelFormat}"; + + public override void SetOutputOptions(FFMpegArgumentOptions options) + { + base.SetOutputOptions(options); + options.WithFastStart().WithVideoCodec(Codec).WithConstantRateFactor(CRF).ForcePixelFormat(PixelFormat); + } + } + + public class Mp4ExporterProperty(FFmpegVideoExporter exporter) : FFmpegVideoExporterProperty(exporter) { [Browsable(false)] public override Mp4Exporter Exporter => (Mp4Exporter)base.Exporter; diff --git a/SpineViewer/Exporter/VideoExporter.cs b/SpineViewer/Spine/SpineExporter/VideoExporter.cs similarity index 75% rename from SpineViewer/Exporter/VideoExporter.cs rename to SpineViewer/Spine/SpineExporter/VideoExporter.cs index a36ea2b..04bc6fe 100644 --- a/SpineViewer/Exporter/VideoExporter.cs +++ b/SpineViewer/Spine/SpineExporter/VideoExporter.cs @@ -6,7 +6,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace SpineViewer.Exporter +namespace SpineViewer.Spine.SpineExporter { /// /// 视频导出基类 @@ -41,7 +41,7 @@ namespace SpineViewer.Exporter /// /// 生成单个模型的帧序列 /// - protected IEnumerable GetFrames(Spine.SpineObject spine, BackgroundWorker? worker = null) + protected IEnumerable GetFrames(SpineObject spine, BackgroundWorker? worker = null) { // 独立导出时如果 Duration 小于 0 则使用所有轨道上动画时长最大值 var duration = Duration; @@ -51,7 +51,7 @@ namespace SpineViewer.Exporter int total = (int)(duration * FPS); // 完整帧的数量 float deltaFinal = duration - delta * total; // 最后一帧时长 - int final = (KeepLast && (deltaFinal > 1e-3)) ? 1 : 0; + int final = KeepLast && deltaFinal > 1e-3 ? 1 : 0; int frameCount = 1 + total + final; // 所有帧的数量 = 起始帧 + 完整帧 + 最后一帧 @@ -90,7 +90,7 @@ namespace SpineViewer.Exporter /// /// 生成多个模型的帧序列 /// - protected IEnumerable GetFrames(Spine.SpineObject[] spinesToRender, BackgroundWorker? worker = null) + protected IEnumerable GetFrames(SpineObject[] spinesToRender, BackgroundWorker? worker = null) { // 导出单个时必须根据 Duration 决定导出时长 var duration = Duration; @@ -99,7 +99,7 @@ namespace SpineViewer.Exporter int total = (int)(duration * FPS); // 完整帧的数量 float deltaFinal = duration - delta * total; // 最后一帧时长 - int final = (KeepLast && (deltaFinal > 1e-3)) ? 1 : 0; + int final = KeepLast && deltaFinal > 1e-3 ? 1 : 0; int frameCount = 1 + total + final; // 所有帧的数量 = 起始帧 + 完整帧 + 最后一帧 @@ -135,11 +135,35 @@ namespace SpineViewer.Exporter } } - public override void Export(Spine.SpineObject[] spines, BackgroundWorker? worker = null) + public override void Export(SpineObject[] spines, BackgroundWorker? worker = null) { // 导出视频格式需要把模型时间都重置到 0 foreach (var spine in spines) spine.ResetAnimationsTime(); base.Export(spines, worker); } } + + public class VideoExporterProperty(VideoExporter exporter) : ExporterProperty(exporter) + { + [Browsable(false)] + public override VideoExporter Exporter => (VideoExporter)base.Exporter; + + /// + /// 导出时长 + /// + [Category("[1] 视频参数"), DisplayName("时长"), Description("可以从模型列表查看动画时长, 如果小于 0, 则在逐个导出时每个模型使用各自的所有轨道动画时长最大值")] + public float Duration { get => Exporter.Duration; set => Exporter.Duration = value; } + + /// + /// 帧率 + /// + [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; } + } } diff --git a/SpineViewer/PropertyGridWrappers/Exporter/WebmExporterWrapper.cs b/SpineViewer/Spine/SpineExporter/WebmExporter.cs similarity index 52% rename from SpineViewer/PropertyGridWrappers/Exporter/WebmExporterWrapper.cs rename to SpineViewer/Spine/SpineExporter/WebmExporter.cs index 12e3cb0..560d8a0 100644 --- a/SpineViewer/PropertyGridWrappers/Exporter/WebmExporterWrapper.cs +++ b/SpineViewer/Spine/SpineExporter/WebmExporter.cs @@ -1,4 +1,5 @@ -using SpineViewer.Exporter; +using FFMpegCore; +using SpineViewer.Utils; using System; using System.Collections.Generic; using System.ComponentModel; @@ -6,9 +7,49 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace SpineViewer.PropertyGridWrappers.Exporter +namespace SpineViewer.Spine.SpineExporter { - public class WebmExporterWrapper(WebmExporter exporter) : FFmpegVideoExporterWrapper(exporter) + /// + /// WebM 导出参数 + /// + public class WebmExporter : FFmpegVideoExporter + { + public WebmExporter() + { + // 默认用透明黑背景 + BackgroundColor = new(0, 0, 0, 0); + } + + public override string Format => "webm"; + + public override string Suffix => ".webm"; + + /// + /// 编码器 + /// + public string Codec { get; set; } = "libvpx-vp9"; + + /// + /// CRF + /// + public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); } + private int crf = 23; + + /// + /// 像素格式 + /// + public string PixelFormat { get; set; } = "yuva420p"; + + public override string FileNameNoteSuffix => $"{Codec}_{CRF}_{PixelFormat}"; + + public override void SetOutputOptions(FFMpegArgumentOptions options) + { + base.SetOutputOptions(options); + options.WithVideoCodec(Codec).WithConstantRateFactor(CRF).ForcePixelFormat(PixelFormat); + } + } + + public class WebmExporterProperty(WebmExporter exporter) : FFmpegVideoExporterProperty(exporter) { [Browsable(false)] public override WebmExporter Exporter => (WebmExporter)base.Exporter; diff --git a/SpineViewer/PropertyGridWrappers/Exporter/WebpExporterWrapper.cs b/SpineViewer/Spine/SpineExporter/WebpExporter.cs similarity index 53% rename from SpineViewer/PropertyGridWrappers/Exporter/WebpExporterWrapper.cs rename to SpineViewer/Spine/SpineExporter/WebpExporter.cs index ab3696e..fc62228 100644 --- a/SpineViewer/PropertyGridWrappers/Exporter/WebpExporterWrapper.cs +++ b/SpineViewer/Spine/SpineExporter/WebpExporter.cs @@ -1,4 +1,5 @@ -using SpineViewer.Exporter; +using FFMpegCore; +using SpineViewer.Utils; using System; using System.Collections.Generic; using System.ComponentModel; @@ -6,9 +7,59 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace SpineViewer.PropertyGridWrappers.Exporter +namespace SpineViewer.Spine.SpineExporter { - public class WebpExporterWrapper(WebpExporter exporter) : FFmpegVideoExporterWrapper(exporter) + /// + /// MP4 导出参数 + /// + public class WebpExporter : FFmpegVideoExporter + { + public WebpExporter() + { + FPS = 24; + } + + public override string Format => "webp"; + + public override string Suffix => ".webp"; + + /// + /// 编码器 + /// + public string Codec { get; set; } = "libwebp_anim"; + + /// + /// 是否无损 + /// + public bool Lossless { get; set; } = false; + + /// + /// 质量 + /// + public int Quality { get => quality; set => quality = Math.Clamp(value, 0, 100); } + private int quality = 75; + + /// + /// 像素格式 + /// + public string PixelFormat { get; set; } = "yuva420p"; + + /// + /// 循环次数, 0 无限循环, 取值范围 [0, 65535] + /// + public int Loop { get => loop; set => loop = Math.Clamp(value, 0, 65535); } + private int loop = 0; + + public override string FileNameNoteSuffix => $"{Codec}_{Quality}_{PixelFormat}"; + + public override void SetOutputOptions(FFMpegArgumentOptions options) + { + base.SetOutputOptions(options); + options.WithVideoCodec(Codec).ForcePixelFormat(PixelFormat).WithCustomArgument($"-lossless {(Lossless ? 1 : 0)} -quality {Quality} -loop {Loop}"); + } + } + + public class WebpExporterProperty(WebpExporter exporter) : FFmpegVideoExporterProperty(exporter) { [Browsable(false)] public override WebpExporter Exporter => (WebpExporter)base.Exporter; diff --git a/SpineViewer/Utils/ImplementationResolver.cs b/SpineViewer/Utils/ImplementationResolver.cs index eb1a1aa..be6d69e 100644 --- a/SpineViewer/Utils/ImplementationResolver.cs +++ b/SpineViewer/Utils/ImplementationResolver.cs @@ -1,5 +1,4 @@ -using SpineViewer.Exporter; -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Reflection; diff --git a/SpineViewer/PropertyGridWrappers/TypeConverter.cs b/SpineViewer/Utils/TypeConverter.cs similarity index 99% rename from SpineViewer/PropertyGridWrappers/TypeConverter.cs rename to SpineViewer/Utils/TypeConverter.cs index 27f7592..6a5a577 100644 --- a/SpineViewer/PropertyGridWrappers/TypeConverter.cs +++ b/SpineViewer/Utils/TypeConverter.cs @@ -10,7 +10,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace SpineViewer.PropertyGridWrappers +namespace SpineViewer.Utils { public class PointFConverter : ExpandableObjectConverter { diff --git a/SpineViewer/PropertyGridWrappers/UITypeEditor.cs b/SpineViewer/Utils/UITypeEditor.cs similarity index 99% rename from SpineViewer/PropertyGridWrappers/UITypeEditor.cs rename to SpineViewer/Utils/UITypeEditor.cs index bc2dd4d..94f9720 100644 --- a/SpineViewer/PropertyGridWrappers/UITypeEditor.cs +++ b/SpineViewer/Utils/UITypeEditor.cs @@ -9,7 +9,7 @@ using System.Text; using System.Threading.Tasks; using System.Windows.Forms.Design; -namespace SpineViewer.PropertyGridWrappers +namespace SpineViewer.Utils { /// /// 使用 FolderBrowserDialog 的文件夹路径编辑器