small change
This commit is contained in:
@@ -51,7 +51,7 @@ namespace SpineViewer.Controls
|
|||||||
{
|
{
|
||||||
propertyGrid = value;
|
propertyGrid = value;
|
||||||
if (propertyGrid is not null)
|
if (propertyGrid is not null)
|
||||||
propertyGrid.SelectedObject = new PropertyGridWrappers.SpinePreviewerWrapper(this);
|
propertyGrid.SelectedObject = new SpinePreviewerProperty(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private PropertyGrid? propertyGrid;
|
private PropertyGrid? propertyGrid;
|
||||||
@@ -635,4 +635,42 @@ namespace SpineViewer.Controls
|
|||||||
//public void ClickForwardStepButton() => button_ForwardStep_Click(button_ForwardStep, EventArgs.Empty);
|
//public void ClickForwardStepButton() => button_ForwardStep_Click(button_ForwardStep, EventArgs.Empty);
|
||||||
//public void ClickForwardFastButton() => button_ForwardFast_Click(button_ForwardFast, EventArgs.Empty);
|
//public void ClickForwardFastButton() => button_ForwardFast_Click(button_ForwardFast, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用于在 PropertyGrid 上显示 SpinePreviewe 属性的包装类, 提供用户操作接口
|
||||||
|
/// </summary>
|
||||||
|
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; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
using SpineViewer.PropertyGridWrappers.Exporter;
|
using SpineViewer.Utils;
|
||||||
using SpineViewer.Utils;
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -8,14 +7,15 @@ using System.Data;
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using SpineViewer.Spine.SpineExporter;
|
||||||
|
|
||||||
namespace SpineViewer.Dialogs
|
namespace SpineViewer.Dialogs
|
||||||
{
|
{
|
||||||
public partial class ExportDialog : Form
|
public partial class ExportDialog : Form
|
||||||
{
|
{
|
||||||
private readonly ExporterWrapper wrapper;
|
private readonly ExporterProperty wrapper;
|
||||||
|
|
||||||
public ExportDialog(ExporterWrapper wrapper)
|
public ExportDialog(ExporterProperty wrapper)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
this.wrapper = wrapper;
|
this.wrapper = wrapper;
|
||||||
|
|||||||
@@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// MP4 导出参数
|
|
||||||
/// </summary>
|
|
||||||
public class AvifExporter : FFmpegVideoExporter
|
|
||||||
{
|
|
||||||
public AvifExporter()
|
|
||||||
{
|
|
||||||
FPS = 24;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string Format => "avif";
|
|
||||||
|
|
||||||
public override string Suffix => ".avif";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 编码器
|
|
||||||
/// </summary>
|
|
||||||
public string Codec { get; set; } = "av1_nvenc";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// CRF
|
|
||||||
/// </summary>
|
|
||||||
public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); }
|
|
||||||
private int crf = 23;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 像素格式
|
|
||||||
/// </summary>
|
|
||||||
public string PixelFormat { get; set; } = "yuv420p";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 循环次数, 0 无限循环, 取值范围 [0, 65535]
|
|
||||||
/// </summary>
|
|
||||||
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}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// FFmpeg 自定义视频导出参数
|
|
||||||
/// </summary>
|
|
||||||
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;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文件格式
|
|
||||||
/// </summary>
|
|
||||||
public string CustomFormat { get; set; } = "mp4";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文件名后缀
|
|
||||||
/// </summary>
|
|
||||||
public string CustomSuffix { get; set; } = ".mp4";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// MKV 导出参数
|
|
||||||
/// </summary>
|
|
||||||
public class MkvExporter : FFmpegVideoExporter
|
|
||||||
{
|
|
||||||
public MkvExporter()
|
|
||||||
{
|
|
||||||
BackgroundColor = new(0, 255, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string Format => "matroska";
|
|
||||||
|
|
||||||
public override string Suffix => ".mkv";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 编码器
|
|
||||||
/// </summary>
|
|
||||||
public string Codec { get; set; } = "libx265";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// CRF
|
|
||||||
/// </summary>
|
|
||||||
public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); }
|
|
||||||
private int crf = 23;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 像素格式
|
|
||||||
/// </summary>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// MOV 导出参数
|
|
||||||
/// </summary>
|
|
||||||
public class MovExporter : FFmpegVideoExporter
|
|
||||||
{
|
|
||||||
public MovExporter()
|
|
||||||
{
|
|
||||||
BackgroundColor = new(0, 255, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string Format => "mov";
|
|
||||||
|
|
||||||
public override string Suffix => ".mov";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 编码器
|
|
||||||
/// </summary>
|
|
||||||
public string Codec { get; set; } = "prores_ks";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 预设
|
|
||||||
/// </summary>
|
|
||||||
public string Profile { get; set; } = "auto";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 像素格式
|
|
||||||
/// </summary>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// MP4 导出参数
|
|
||||||
/// </summary>
|
|
||||||
public class Mp4Exporter : FFmpegVideoExporter
|
|
||||||
{
|
|
||||||
public Mp4Exporter()
|
|
||||||
{
|
|
||||||
BackgroundColor = new(0, 255, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string Format => "mp4";
|
|
||||||
|
|
||||||
public override string Suffix => ".mp4";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 编码器
|
|
||||||
/// </summary>
|
|
||||||
public string Codec { get; set; } = "libx264";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// CRF
|
|
||||||
/// </summary>
|
|
||||||
public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); }
|
|
||||||
private int crf = 23;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 像素格式
|
|
||||||
/// </summary>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// WebM 导出参数
|
|
||||||
/// </summary>
|
|
||||||
public class WebmExporter : FFmpegVideoExporter
|
|
||||||
{
|
|
||||||
public WebmExporter()
|
|
||||||
{
|
|
||||||
// 默认用透明黑背景
|
|
||||||
BackgroundColor = new(0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string Format => "webm";
|
|
||||||
|
|
||||||
public override string Suffix => ".webm";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 编码器
|
|
||||||
/// </summary>
|
|
||||||
public string Codec { get; set; } = "libvpx-vp9";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// CRF
|
|
||||||
/// </summary>
|
|
||||||
public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); }
|
|
||||||
private int crf = 23;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 像素格式
|
|
||||||
/// </summary>
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// MP4 导出参数
|
|
||||||
/// </summary>
|
|
||||||
public class WebpExporter : FFmpegVideoExporter
|
|
||||||
{
|
|
||||||
public WebpExporter()
|
|
||||||
{
|
|
||||||
FPS = 24;
|
|
||||||
}
|
|
||||||
|
|
||||||
public override string Format => "webp";
|
|
||||||
|
|
||||||
public override string Suffix => ".webp";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 编码器
|
|
||||||
/// </summary>
|
|
||||||
public string Codec { get; set; } = "libwebp_anim";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 是否无损
|
|
||||||
/// </summary>
|
|
||||||
public bool Lossless { get; set; } = false;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 质量
|
|
||||||
/// </summary>
|
|
||||||
public int Quality { get => quality; set => quality = Math.Clamp(value, 0, 100); }
|
|
||||||
private int quality = 75;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 像素格式
|
|
||||||
/// </summary>
|
|
||||||
public string PixelFormat { get; set; } = "yuva420p";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 循环次数, 0 无限循环, 取值范围 [0, 65535]
|
|
||||||
/// </summary>
|
|
||||||
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}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -2,11 +2,9 @@
|
|||||||
using SpineViewer.Spine;
|
using SpineViewer.Spine;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using SpineViewer.Exporter;
|
|
||||||
using System.Reflection.Metadata;
|
|
||||||
using SpineViewer.PropertyGridWrappers.Exporter;
|
|
||||||
using SpineViewer.Natives;
|
using SpineViewer.Natives;
|
||||||
using SpineViewer.Utils;
|
using SpineViewer.Utils;
|
||||||
|
using SpineViewer.Spine.SpineExporter;
|
||||||
|
|
||||||
namespace SpineViewer
|
namespace SpineViewer
|
||||||
{
|
{
|
||||||
@@ -14,7 +12,7 @@ namespace SpineViewer
|
|||||||
{
|
{
|
||||||
private readonly Logger logger = LogManager.GetCurrentClassLogger();
|
private readonly Logger logger = LogManager.GetCurrentClassLogger();
|
||||||
|
|
||||||
private readonly Dictionary<string, Exporter.Exporter> exporterCache = [];
|
private readonly Dictionary<string, Exporter> exporterCache = [];
|
||||||
|
|
||||||
public SpineViewerForm()
|
public SpineViewerForm()
|
||||||
{
|
{
|
||||||
@@ -98,7 +96,7 @@ namespace SpineViewer
|
|||||||
exporter.View = spinePreviewer.GetView();
|
exporter.View = spinePreviewer.GetView();
|
||||||
exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly;
|
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)
|
if (exportDialog.ShowDialog() != DialogResult.OK)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -118,7 +116,7 @@ namespace SpineViewer
|
|||||||
exporter.View = spinePreviewer.GetView();
|
exporter.View = spinePreviewer.GetView();
|
||||||
exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly;
|
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)
|
if (exportDialog.ShowDialog() != DialogResult.OK)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -138,7 +136,7 @@ namespace SpineViewer
|
|||||||
exporter.View = spinePreviewer.GetView();
|
exporter.View = spinePreviewer.GetView();
|
||||||
exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly;
|
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)
|
if (exportDialog.ShowDialog() != DialogResult.OK)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -158,7 +156,7 @@ namespace SpineViewer
|
|||||||
exporter.View = spinePreviewer.GetView();
|
exporter.View = spinePreviewer.GetView();
|
||||||
exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly;
|
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)
|
if (exportDialog.ShowDialog() != DialogResult.OK)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -178,7 +176,7 @@ namespace SpineViewer
|
|||||||
exporter.View = spinePreviewer.GetView();
|
exporter.View = spinePreviewer.GetView();
|
||||||
exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly;
|
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)
|
if (exportDialog.ShowDialog() != DialogResult.OK)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -198,7 +196,7 @@ namespace SpineViewer
|
|||||||
exporter.View = spinePreviewer.GetView();
|
exporter.View = spinePreviewer.GetView();
|
||||||
exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly;
|
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)
|
if (exportDialog.ShowDialog() != DialogResult.OK)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -218,7 +216,7 @@ namespace SpineViewer
|
|||||||
exporter.View = spinePreviewer.GetView();
|
exporter.View = spinePreviewer.GetView();
|
||||||
exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly;
|
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)
|
if (exportDialog.ShowDialog() != DialogResult.OK)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -238,7 +236,7 @@ namespace SpineViewer
|
|||||||
exporter.View = spinePreviewer.GetView();
|
exporter.View = spinePreviewer.GetView();
|
||||||
exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly;
|
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)
|
if (exportDialog.ShowDialog() != DialogResult.OK)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -258,7 +256,7 @@ namespace SpineViewer
|
|||||||
exporter.View = spinePreviewer.GetView();
|
exporter.View = spinePreviewer.GetView();
|
||||||
exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly;
|
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)
|
if (exportDialog.ShowDialog() != DialogResult.OK)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -278,7 +276,7 @@ namespace SpineViewer
|
|||||||
exporter.View = spinePreviewer.GetView();
|
exporter.View = spinePreviewer.GetView();
|
||||||
exporter.RenderSelectedOnly = spinePreviewer.RenderSelectedOnly;
|
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)
|
if (exportDialog.ShowDialog() != DialogResult.OK)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@@ -337,7 +335,7 @@ namespace SpineViewer
|
|||||||
private void Export_Work(object? sender, DoWorkEventArgs e)
|
private void Export_Work(object? sender, DoWorkEventArgs e)
|
||||||
{
|
{
|
||||||
var worker = (BackgroundWorker)sender;
|
var worker = (BackgroundWorker)sender;
|
||||||
var exporter = (Exporter.Exporter)e.Argument;
|
var exporter = (Exporter)e.Argument;
|
||||||
Invoke(() => TaskbarManager.SetProgressState(Handle, TBPFLAG.TBPF_INDETERMINATE));
|
Invoke(() => TaskbarManager.SetProgressState(Handle, TBPFLAG.TBPF_INDETERMINATE));
|
||||||
spinePreviewer.StopRender();
|
spinePreviewer.StopRender();
|
||||||
lock (spineListView.Spines) { exporter.Export(spineListView.Spines.Where(sp => !sp.IsHidden).ToArray(), (BackgroundWorker)sender); }
|
lock (spineListView.Spines) { exporter.Export(spineListView.Spines.Where(sp => !sp.IsHidden).ToArray(), (BackgroundWorker)sender); }
|
||||||
|
|||||||
@@ -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;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 输出文件夹
|
|
||||||
/// </summary>
|
|
||||||
[Editor(typeof(FolderNameEditor), typeof(UITypeEditor))]
|
|
||||||
[Category("[0] 导出"), DisplayName("输出文件夹"), Description("逐个导出时可以留空,将逐个导出到模型自身所在目录")]
|
|
||||||
public string? OutputDir { get => Exporter.OutputDir; set => Exporter.OutputDir = value; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 导出单个
|
|
||||||
/// </summary>
|
|
||||||
[Category("[0] 导出"), DisplayName("导出单个"), Description("是否将模型在同一个画面上导出单个文件,否则逐个导出模型")]
|
|
||||||
public bool IsExportSingle { get => Exporter.IsExportSingle; set => Exporter.IsExportSingle = value; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 画面分辨率
|
|
||||||
/// </summary>
|
|
||||||
[TypeConverter(typeof(SizeConverter))]
|
|
||||||
[Category("[0] 导出"), DisplayName("分辨率"), Description("画面的宽高像素大小,请在预览画面参数面板进行调整")]
|
|
||||||
public Size Resolution { get => Exporter.Resolution; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 渲染视窗
|
|
||||||
/// </summary>
|
|
||||||
[Category("[0] 导出"), DisplayName("视图"), Description("画面的视图参数,请在预览画面参数面板进行调整")]
|
|
||||||
public SFML.Graphics.View View { get => Exporter.View; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 是否仅渲染选中
|
|
||||||
/// </summary>
|
|
||||||
[Category("[0] 导出"), DisplayName("仅渲染选中"), Description("是否仅导出选中的模型,请在预览画面参数面板进行调整")]
|
|
||||||
public bool RenderSelectedOnly { get => Exporter.RenderSelectedOnly; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 背景颜色
|
|
||||||
/// </summary>
|
|
||||||
[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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文件格式
|
|
||||||
/// </summary>
|
|
||||||
[Category("[2] FFmpeg 基本参数"), DisplayName("文件格式"), Description("-f, 文件格式")]
|
|
||||||
public virtual string Format => Exporter.Format;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文件名后缀
|
|
||||||
/// </summary>
|
|
||||||
[Category("[2] FFmpeg 基本参数"), DisplayName("文件名后缀"), Description("文件名后缀")]
|
|
||||||
public virtual string Suffix => Exporter.Suffix;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文件名后缀
|
|
||||||
/// </summary>
|
|
||||||
[Category("[2] FFmpeg 基本参数"), DisplayName("自定义参数"), Description("使用 \"ffmpeg -h encoder=<编码器>\" 查看编码器支持的参数\n使用 \"ffmpeg -h muxer=<文件格式>\" 查看文件格式支持的参数")]
|
|
||||||
public string CustomArgument { get => Exporter.CustomArgument; set => Exporter.CustomArgument = value; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 单帧画面格式
|
|
||||||
/// </summary>
|
|
||||||
[TypeConverter(typeof(ImageFormatConverter))]
|
|
||||||
[Category("[1] 单帧画面"), DisplayName("图像格式")]
|
|
||||||
public ImageFormat ImageFormat { get => Exporter.ImageFormat; set => Exporter.ImageFormat = value; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文件名后缀
|
|
||||||
/// </summary>
|
|
||||||
[Category("[1] 单帧画面"), DisplayName("文件名后缀"), Description("与图像格式匹配的文件名后缀")]
|
|
||||||
public string Suffix { get => Exporter.ImageFormat.GetSuffix(); }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// DPI
|
|
||||||
/// </summary>
|
|
||||||
[TypeConverter(typeof(SizeFConverter))]
|
|
||||||
[Category("[1] 单帧画面"), DisplayName("DPI"), Description("导出图像的每英寸像素数,用于调整图像的物理尺寸")]
|
|
||||||
public SizeF DPI { get => Exporter.DPI; set => Exporter.DPI = value; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 文件名后缀
|
|
||||||
/// </summary>
|
|
||||||
[TypeConverter(typeof(StringEnumConverter)), StringEnumConverter.StandardValues(".png", ".jpg", ".tga", ".bmp")]
|
|
||||||
[Category("[2] 帧序列参数"), DisplayName("文件名后缀"), Description("帧文件的后缀,同时决定帧图像格式")]
|
|
||||||
public string Suffix { get => Exporter.Suffix; set => Exporter.Suffix = value; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 调色板最大颜色数量
|
|
||||||
/// </summary>
|
|
||||||
[Category("[3] 格式参数"), DisplayName("调色板最大颜色数量"), Description("设置调色板使用的最大颜色数量, 越多则色彩保留程度越高")]
|
|
||||||
public uint MaxColors { get => Exporter.MaxColors; set => Exporter.MaxColors = value; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 透明度阈值
|
|
||||||
/// </summary>
|
|
||||||
[Category("[3] 格式参数"), DisplayName("透明度阈值"), Description("小于该值的像素点会被认为是透明像素")]
|
|
||||||
public byte AlphaThreshold { get => Exporter.AlphaThreshold; set => Exporter.AlphaThreshold = value; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 透明度阈值
|
|
||||||
/// </summary>
|
|
||||||
[Category("[3] 格式参数"), DisplayName("循环次数"), Description("-loop, 循环次数, -1 不循环, 0 无限循环, 取值范围 [-1, 65535]")]
|
|
||||||
public int Loop { get => Exporter.Loop; set => Exporter.Loop = value; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 导出时长
|
|
||||||
/// </summary>
|
|
||||||
[Category("[1] 视频参数"), DisplayName("时长"), Description("可以从模型列表查看动画时长, 如果小于 0, 则在逐个导出时每个模型使用各自的所有轨道动画时长最大值")]
|
|
||||||
public float Duration { get => Exporter.Duration; set => Exporter.Duration = value; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 帧率
|
|
||||||
/// </summary>
|
|
||||||
[Category("[1] 视频参数"), DisplayName("帧率"), Description("每秒画面数")]
|
|
||||||
public float FPS { get => Exporter.FPS; set => Exporter.FPS = value; }
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 保留最后一帧
|
|
||||||
/// </summary>
|
|
||||||
[Category("[1] 视频参数"), DisplayName("保留最后一帧"), Description("当设置保留最后一帧时, 动图会更为连贯, 但是帧数可能比预期帧数多 1")]
|
|
||||||
public bool KeepLast { get => Exporter.KeepLast; set => Exporter.KeepLast = value; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using SpineViewer.Spine;
|
using SpineViewer.Spine;
|
||||||
|
using SpineViewer.Utils;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using SpineViewer.Spine;
|
using SpineViewer.Spine;
|
||||||
|
using SpineViewer.Utils;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using SpineViewer.Spine;
|
using SpineViewer.Spine;
|
||||||
|
using SpineViewer.Utils;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using SpineViewer.Spine;
|
using SpineViewer.Spine;
|
||||||
|
using SpineViewer.Utils;
|
||||||
|
|
||||||
namespace SpineViewer.PropertyGridWrappers.Spine
|
namespace SpineViewer.PropertyGridWrappers.Spine
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// 用于在 PropertyGrid 上显示 SpinePreviewe 属性的包装类
|
|
||||||
/// </summary>
|
|
||||||
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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using SpineViewer.Exporter;
|
using FFMpegCore;
|
||||||
|
using SpineViewer.Utils;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
@@ -6,9 +7,54 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SpineViewer.PropertyGridWrappers.Exporter
|
namespace SpineViewer.Spine.SpineExporter
|
||||||
{
|
{
|
||||||
public class AvifExporterWrapper(AvifExporter exporter) : FFmpegVideoExporterWrapper(exporter)
|
/// <summary>
|
||||||
|
/// MP4 导出参数
|
||||||
|
/// </summary>
|
||||||
|
public class AvifExporter : FFmpegVideoExporter
|
||||||
|
{
|
||||||
|
public AvifExporter()
|
||||||
|
{
|
||||||
|
FPS = 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Format => "avif";
|
||||||
|
|
||||||
|
public override string Suffix => ".avif";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 编码器
|
||||||
|
/// </summary>
|
||||||
|
public string Codec { get; set; } = "av1_nvenc";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CRF
|
||||||
|
/// </summary>
|
||||||
|
public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); }
|
||||||
|
private int crf = 23;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 像素格式
|
||||||
|
/// </summary>
|
||||||
|
public string PixelFormat { get; set; } = "yuv420p";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 循环次数, 0 无限循环, 取值范围 [0, 65535]
|
||||||
|
/// </summary>
|
||||||
|
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)]
|
[Browsable(false)]
|
||||||
public override AvifExporter Exporter => (AvifExporter)base.Exporter;
|
public override AvifExporter Exporter => (AvifExporter)base.Exporter;
|
||||||
@@ -1,14 +1,40 @@
|
|||||||
using SpineViewer.Exporter;
|
using System;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SpineViewer.PropertyGridWrappers.Exporter
|
namespace SpineViewer.Spine.SpineExporter
|
||||||
{
|
{
|
||||||
public class CustomExporterWrapper(CustomExporter exporter) : FFmpegVideoExporterWrapper(exporter)
|
/// <summary>
|
||||||
|
/// FFmpeg 自定义视频导出参数
|
||||||
|
/// </summary>
|
||||||
|
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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 文件格式
|
||||||
|
/// </summary>
|
||||||
|
public string CustomFormat { get; set; } = "mp4";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 文件名后缀
|
||||||
|
/// </summary>
|
||||||
|
public string CustomSuffix { get; set; } = ".mp4";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class CustomExporterProperty(CustomExporter exporter) : FFmpegVideoExporterProperty(exporter)
|
||||||
{
|
{
|
||||||
[Browsable(false)]
|
[Browsable(false)]
|
||||||
public override CustomExporter Exporter => (CustomExporter)base.Exporter;
|
public override CustomExporter Exporter => (CustomExporter)base.Exporter;
|
||||||
@@ -6,7 +6,7 @@ using System.Drawing.Imaging;
|
|||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
|
||||||
namespace SpineViewer.Exporter
|
namespace SpineViewer.Spine.SpineExporter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// SFML.Graphics.Image 帧对象包装类, 将接管给定的 image 对象生命周期
|
/// SFML.Graphics.Image 帧对象包装类, 将接管给定的 image 对象生命周期
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
using NLog;
|
using NLog;
|
||||||
using SpineViewer.Extensions;
|
using SpineViewer.Extensions;
|
||||||
using SpineViewer.PropertyGridWrappers;
|
|
||||||
using SpineViewer.Utils;
|
using SpineViewer.Utils;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -11,7 +10,7 @@ using System.Reflection;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SpineViewer.Exporter
|
namespace SpineViewer.Spine.SpineExporter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 导出器基类
|
/// 导出器基类
|
||||||
@@ -96,12 +95,12 @@ namespace SpineViewer.Exporter
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取单个模型的单帧画面
|
/// 获取单个模型的单帧画面
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected SFMLImageVideoFrame GetFrame(Spine.SpineObject spine) => GetFrame([spine]);
|
protected SFMLImageVideoFrame GetFrame(SpineObject spine) => GetFrame([spine]);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 获取模型列表的单帧画面
|
/// 获取模型列表的单帧画面
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected SFMLImageVideoFrame GetFrame(Spine.SpineObject[] spinesToRender)
|
protected SFMLImageVideoFrame GetFrame(SpineObject[] spinesToRender)
|
||||||
{
|
{
|
||||||
// RenderTexture 必须临时创建, 随用随取, 防止出现跨线程的情况
|
// RenderTexture 必须临时创建, 随用随取, 防止出现跨线程的情况
|
||||||
using var texPma = GetRenderTexture();
|
using var texPma = GetRenderTexture();
|
||||||
@@ -149,12 +148,12 @@ namespace SpineViewer.Exporter
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 每个模型在同一个画面进行导出
|
/// 每个模型在同一个画面进行导出
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected abstract void ExportSingle(Spine.SpineObject[] spinesToRender, BackgroundWorker? worker = null);
|
protected abstract void ExportSingle(SpineObject[] spinesToRender, BackgroundWorker? worker = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 每个模型独立导出
|
/// 每个模型独立导出
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected abstract void ExportIndividual(Spine.SpineObject[] spinesToRender, BackgroundWorker? worker = null);
|
protected abstract void ExportIndividual(SpineObject[] spinesToRender, BackgroundWorker? worker = null);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 检查参数是否合法并规范化参数值, 否则返回用户错误原因
|
/// 检查参数是否合法并规范化参数值, 否则返回用户错误原因
|
||||||
@@ -178,7 +177,7 @@ namespace SpineViewer.Exporter
|
|||||||
/// <param name="spines">要进行导出的 Spine 列表</param>
|
/// <param name="spines">要进行导出的 Spine 列表</param>
|
||||||
/// <param name="worker">用来执行该函数的 worker</param>
|
/// <param name="worker">用来执行该函数的 worker</param>
|
||||||
/// <exception cref="ArgumentException"></exception>
|
/// <exception cref="ArgumentException"></exception>
|
||||||
public virtual void Export(Spine.SpineObject[] spines, BackgroundWorker? worker = null)
|
public virtual void Export(SpineObject[] spines, BackgroundWorker? worker = null)
|
||||||
{
|
{
|
||||||
if (Validate() is string err)
|
if (Validate() is string err)
|
||||||
throw new ArgumentException(err);
|
throw new ArgumentException(err);
|
||||||
@@ -191,4 +190,53 @@ namespace SpineViewer.Exporter
|
|||||||
logger.LogCurrentProcessMemoryUsage();
|
logger.LogCurrentProcessMemoryUsage();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 用于在 PropertyGrid 上提供用户操作接口的包装类
|
||||||
|
/// </summary>
|
||||||
|
public class ExporterProperty(Exporter exporter)
|
||||||
|
{
|
||||||
|
[Browsable(false)]
|
||||||
|
public virtual Exporter Exporter { get; } = exporter;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 输出文件夹
|
||||||
|
/// </summary>
|
||||||
|
[Editor(typeof(FolderNameEditor), typeof(UITypeEditor))]
|
||||||
|
[Category("[0] 导出"), DisplayName("输出文件夹"), Description("逐个导出时可以留空,将逐个导出到模型自身所在目录")]
|
||||||
|
public string? OutputDir { get => Exporter.OutputDir; set => Exporter.OutputDir = value; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 导出单个
|
||||||
|
/// </summary>
|
||||||
|
[Category("[0] 导出"), DisplayName("导出单个"), Description("是否将模型在同一个画面上导出单个文件,否则逐个导出模型")]
|
||||||
|
public bool IsExportSingle { get => Exporter.IsExportSingle; set => Exporter.IsExportSingle = value; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 画面分辨率
|
||||||
|
/// </summary>
|
||||||
|
[TypeConverter(typeof(SizeConverter))]
|
||||||
|
[Category("[0] 导出"), DisplayName("分辨率"), Description("画面的宽高像素大小,请在预览画面参数面板进行调整")]
|
||||||
|
public Size Resolution { get => Exporter.Resolution; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 渲染视窗
|
||||||
|
/// </summary>
|
||||||
|
[Category("[0] 导出"), DisplayName("视图"), Description("画面的视图参数,请在预览画面参数面板进行调整")]
|
||||||
|
public SFML.Graphics.View View { get => Exporter.View; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否仅渲染选中
|
||||||
|
/// </summary>
|
||||||
|
[Category("[0] 导出"), DisplayName("仅渲染选中"), Description("是否仅导出选中的模型,请在预览画面参数面板进行调整")]
|
||||||
|
public bool RenderSelectedOnly { get => Exporter.RenderSelectedOnly; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 背景颜色
|
||||||
|
/// </summary>
|
||||||
|
[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; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -8,7 +8,7 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace SpineViewer.Exporter
|
namespace SpineViewer.Spine.SpineExporter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 使用 FFmpeg 的视频导出器
|
/// 使用 FFmpeg 的视频导出器
|
||||||
@@ -51,7 +51,7 @@ namespace SpineViewer.Exporter
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void ExportSingle(Spine.SpineObject[] spinesToRender, BackgroundWorker? worker = null)
|
protected override void ExportSingle(SpineObject[] spinesToRender, BackgroundWorker? worker = null)
|
||||||
{
|
{
|
||||||
var noteSuffix = FileNameNoteSuffix;
|
var noteSuffix = FileNameNoteSuffix;
|
||||||
if (!string.IsNullOrWhiteSpace(noteSuffix)) noteSuffix = $"_{noteSuffix}";
|
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;
|
var noteSuffix = FileNameNoteSuffix;
|
||||||
if (!string.IsNullOrWhiteSpace(noteSuffix)) noteSuffix = $"_{noteSuffix}";
|
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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 文件格式
|
||||||
|
/// </summary>
|
||||||
|
[Category("[2] FFmpeg 基本参数"), DisplayName("文件格式"), Description("-f, 文件格式")]
|
||||||
|
public virtual string Format => Exporter.Format;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 文件名后缀
|
||||||
|
/// </summary>
|
||||||
|
[Category("[2] FFmpeg 基本参数"), DisplayName("文件名后缀"), Description("文件名后缀")]
|
||||||
|
public virtual string Suffix => Exporter.Suffix;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 文件名后缀
|
||||||
|
/// </summary>
|
||||||
|
[Category("[2] FFmpeg 基本参数"), DisplayName("自定义参数"), Description("使用 \"ffmpeg -h encoder=<编码器>\" 查看编码器支持的参数\n使用 \"ffmpeg -h muxer=<文件格式>\" 查看文件格式支持的参数")]
|
||||||
|
public string CustomArgument { get => Exporter.CustomArgument; set => Exporter.CustomArgument = value; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -7,7 +7,7 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SpineViewer.Exporter
|
namespace SpineViewer.Spine.SpineExporter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 单帧画面导出器
|
/// 单帧画面导出器
|
||||||
@@ -43,7 +43,7 @@ namespace SpineViewer.Exporter
|
|||||||
}
|
}
|
||||||
private SizeF dpi = new(144, 144);
|
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()}";
|
var filename = $"frame_{timestamp}{ImageFormat.GetSuffix()}";
|
||||||
@@ -65,7 +65,7 @@ namespace SpineViewer.Exporter
|
|||||||
worker?.ReportProgress(100, $"已处理 1/1");
|
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 total = spinesToRender.Length;
|
||||||
int success = 0;
|
int success = 0;
|
||||||
@@ -104,4 +104,30 @@ namespace SpineViewer.Exporter
|
|||||||
logger.Info("{} frames saved successfully", success);
|
logger.Info("{} frames saved successfully", success);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class FrameExporterProperty(FrameExporter exporter) : ExporterProperty(exporter)
|
||||||
|
{
|
||||||
|
[Browsable(false)]
|
||||||
|
public override FrameExporter Exporter => (FrameExporter)base.Exporter;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 单帧画面格式
|
||||||
|
/// </summary>
|
||||||
|
[TypeConverter(typeof(ImageFormatConverter))]
|
||||||
|
[Category("[1] 单帧画面"), DisplayName("图像格式")]
|
||||||
|
public ImageFormat ImageFormat { get => Exporter.ImageFormat; set => Exporter.ImageFormat = value; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 文件名后缀
|
||||||
|
/// </summary>
|
||||||
|
[Category("[1] 单帧画面"), DisplayName("文件名后缀"), Description("与图像格式匹配的文件名后缀")]
|
||||||
|
public string Suffix { get => Exporter.ImageFormat.GetSuffix(); }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// DPI
|
||||||
|
/// </summary>
|
||||||
|
[TypeConverter(typeof(SizeFConverter))]
|
||||||
|
[Category("[1] 单帧画面"), DisplayName("DPI"), Description("导出图像的每英寸像素数,用于调整图像的物理尺寸")]
|
||||||
|
public SizeF DPI { get => Exporter.DPI; set => Exporter.DPI = value; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using SpineViewer.Spine;
|
using SpineViewer.Spine;
|
||||||
|
using SpineViewer.Utils;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
@@ -6,7 +7,7 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SpineViewer.Exporter
|
namespace SpineViewer.Spine.SpineExporter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 帧序列导出器
|
/// 帧序列导出器
|
||||||
@@ -18,7 +19,7 @@ namespace SpineViewer.Exporter
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string Suffix { get; set; } = ".png";
|
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}");
|
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)
|
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;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 文件名后缀
|
||||||
|
/// </summary>
|
||||||
|
[TypeConverter(typeof(StringEnumConverter)), StringEnumConverter.StandardValues(".png", ".jpg", ".tga", ".bmp")]
|
||||||
|
[Category("[2] 帧序列参数"), DisplayName("文件名后缀"), Description("帧文件的后缀,同时决定帧图像格式")]
|
||||||
|
public string Suffix { get => Exporter.Suffix; set => Exporter.Suffix = value; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -6,7 +6,7 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SpineViewer.Exporter
|
namespace SpineViewer.Spine.SpineExporter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// GIF 导出参数
|
/// GIF 导出参数
|
||||||
@@ -52,4 +52,28 @@ namespace SpineViewer.Exporter
|
|||||||
options.WithCustomArgument(customArgs);
|
options.WithCustomArgument(customArgs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class GifExporterProperty(FFmpegVideoExporter exporter) : FFmpegVideoExporterProperty(exporter)
|
||||||
|
{
|
||||||
|
[Browsable(false)]
|
||||||
|
public override GifExporter Exporter => (GifExporter)base.Exporter;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 调色板最大颜色数量
|
||||||
|
/// </summary>
|
||||||
|
[Category("[3] 格式参数"), DisplayName("调色板最大颜色数量"), Description("设置调色板使用的最大颜色数量, 越多则色彩保留程度越高")]
|
||||||
|
public uint MaxColors { get => Exporter.MaxColors; set => Exporter.MaxColors = value; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 透明度阈值
|
||||||
|
/// </summary>
|
||||||
|
[Category("[3] 格式参数"), DisplayName("透明度阈值"), Description("小于该值的像素点会被认为是透明像素")]
|
||||||
|
public byte AlphaThreshold { get => Exporter.AlphaThreshold; set => Exporter.AlphaThreshold = value; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 透明度阈值
|
||||||
|
/// </summary>
|
||||||
|
[Category("[3] 格式参数"), DisplayName("循环次数"), Description("-loop, 循环次数, -1 不循环, 0 无限循环, 取值范围 [-1, 65535]")]
|
||||||
|
public int Loop { get => Exporter.Loop; set => Exporter.Loop = value; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using SpineViewer.Exporter;
|
using FFMpegCore;
|
||||||
|
using SpineViewer.Utils;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
@@ -6,9 +7,48 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SpineViewer.PropertyGridWrappers.Exporter
|
namespace SpineViewer.Spine.SpineExporter
|
||||||
{
|
{
|
||||||
public class MkvExporterWrapper(FFmpegVideoExporter exporter) : FFmpegVideoExporterWrapper(exporter)
|
/// <summary>
|
||||||
|
/// MKV 导出参数
|
||||||
|
/// </summary>
|
||||||
|
public class MkvExporter : FFmpegVideoExporter
|
||||||
|
{
|
||||||
|
public MkvExporter()
|
||||||
|
{
|
||||||
|
BackgroundColor = new(0, 255, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Format => "matroska";
|
||||||
|
|
||||||
|
public override string Suffix => ".mkv";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 编码器
|
||||||
|
/// </summary>
|
||||||
|
public string Codec { get; set; } = "libx265";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CRF
|
||||||
|
/// </summary>
|
||||||
|
public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); }
|
||||||
|
private int crf = 23;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 像素格式
|
||||||
|
/// </summary>
|
||||||
|
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)]
|
[Browsable(false)]
|
||||||
public override MkvExporter Exporter => (MkvExporter)base.Exporter;
|
public override MkvExporter Exporter => (MkvExporter)base.Exporter;
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using SpineViewer.Exporter;
|
using FFMpegCore;
|
||||||
|
using SpineViewer.Utils;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
@@ -6,9 +7,47 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SpineViewer.PropertyGridWrappers.Exporter
|
namespace SpineViewer.Spine.SpineExporter
|
||||||
{
|
{
|
||||||
public class MovExporterWrapper(FFmpegVideoExporter exporter) : FFmpegVideoExporterWrapper(exporter)
|
/// <summary>
|
||||||
|
/// MOV 导出参数
|
||||||
|
/// </summary>
|
||||||
|
public class MovExporter : FFmpegVideoExporter
|
||||||
|
{
|
||||||
|
public MovExporter()
|
||||||
|
{
|
||||||
|
BackgroundColor = new(0, 255, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Format => "mov";
|
||||||
|
|
||||||
|
public override string Suffix => ".mov";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 编码器
|
||||||
|
/// </summary>
|
||||||
|
public string Codec { get; set; } = "prores_ks";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 预设
|
||||||
|
/// </summary>
|
||||||
|
public string Profile { get; set; } = "auto";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 像素格式
|
||||||
|
/// </summary>
|
||||||
|
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)]
|
[Browsable(false)]
|
||||||
public override MovExporter Exporter => (MovExporter)base.Exporter;
|
public override MovExporter Exporter => (MovExporter)base.Exporter;
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using SpineViewer.Exporter;
|
using FFMpegCore;
|
||||||
|
using SpineViewer.Utils;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
@@ -6,9 +7,48 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SpineViewer.PropertyGridWrappers.Exporter
|
namespace SpineViewer.Spine.SpineExporter
|
||||||
{
|
{
|
||||||
public class Mp4ExporterWrapper(FFmpegVideoExporter exporter) : FFmpegVideoExporterWrapper(exporter)
|
/// <summary>
|
||||||
|
/// MP4 导出参数
|
||||||
|
/// </summary>
|
||||||
|
public class Mp4Exporter : FFmpegVideoExporter
|
||||||
|
{
|
||||||
|
public Mp4Exporter()
|
||||||
|
{
|
||||||
|
BackgroundColor = new(0, 255, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Format => "mp4";
|
||||||
|
|
||||||
|
public override string Suffix => ".mp4";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 编码器
|
||||||
|
/// </summary>
|
||||||
|
public string Codec { get; set; } = "libx264";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CRF
|
||||||
|
/// </summary>
|
||||||
|
public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); }
|
||||||
|
private int crf = 23;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 像素格式
|
||||||
|
/// </summary>
|
||||||
|
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)]
|
[Browsable(false)]
|
||||||
public override Mp4Exporter Exporter => (Mp4Exporter)base.Exporter;
|
public override Mp4Exporter Exporter => (Mp4Exporter)base.Exporter;
|
||||||
@@ -6,7 +6,7 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SpineViewer.Exporter
|
namespace SpineViewer.Spine.SpineExporter
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 视频导出基类
|
/// 视频导出基类
|
||||||
@@ -41,7 +41,7 @@ namespace SpineViewer.Exporter
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 生成单个模型的帧序列
|
/// 生成单个模型的帧序列
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected IEnumerable<SFMLImageVideoFrame> GetFrames(Spine.SpineObject spine, BackgroundWorker? worker = null)
|
protected IEnumerable<SFMLImageVideoFrame> GetFrames(SpineObject spine, BackgroundWorker? worker = null)
|
||||||
{
|
{
|
||||||
// 独立导出时如果 Duration 小于 0 则使用所有轨道上动画时长最大值
|
// 独立导出时如果 Duration 小于 0 则使用所有轨道上动画时长最大值
|
||||||
var duration = Duration;
|
var duration = Duration;
|
||||||
@@ -51,7 +51,7 @@ namespace SpineViewer.Exporter
|
|||||||
int total = (int)(duration * FPS); // 完整帧的数量
|
int total = (int)(duration * FPS); // 完整帧的数量
|
||||||
|
|
||||||
float deltaFinal = duration - delta * total; // 最后一帧时长
|
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; // 所有帧的数量 = 起始帧 + 完整帧 + 最后一帧
|
int frameCount = 1 + total + final; // 所有帧的数量 = 起始帧 + 完整帧 + 最后一帧
|
||||||
|
|
||||||
@@ -90,7 +90,7 @@ namespace SpineViewer.Exporter
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 生成多个模型的帧序列
|
/// 生成多个模型的帧序列
|
||||||
/// </summary>
|
/// </summary>
|
||||||
protected IEnumerable<SFMLImageVideoFrame> GetFrames(Spine.SpineObject[] spinesToRender, BackgroundWorker? worker = null)
|
protected IEnumerable<SFMLImageVideoFrame> GetFrames(SpineObject[] spinesToRender, BackgroundWorker? worker = null)
|
||||||
{
|
{
|
||||||
// 导出单个时必须根据 Duration 决定导出时长
|
// 导出单个时必须根据 Duration 决定导出时长
|
||||||
var duration = Duration;
|
var duration = Duration;
|
||||||
@@ -99,7 +99,7 @@ namespace SpineViewer.Exporter
|
|||||||
int total = (int)(duration * FPS); // 完整帧的数量
|
int total = (int)(duration * FPS); // 完整帧的数量
|
||||||
|
|
||||||
float deltaFinal = duration - delta * total; // 最后一帧时长
|
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; // 所有帧的数量 = 起始帧 + 完整帧 + 最后一帧
|
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
|
// 导出视频格式需要把模型时间都重置到 0
|
||||||
foreach (var spine in spines) spine.ResetAnimationsTime();
|
foreach (var spine in spines) spine.ResetAnimationsTime();
|
||||||
base.Export(spines, worker);
|
base.Export(spines, worker);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class VideoExporterProperty(VideoExporter exporter) : ExporterProperty(exporter)
|
||||||
|
{
|
||||||
|
[Browsable(false)]
|
||||||
|
public override VideoExporter Exporter => (VideoExporter)base.Exporter;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 导出时长
|
||||||
|
/// </summary>
|
||||||
|
[Category("[1] 视频参数"), DisplayName("时长"), Description("可以从模型列表查看动画时长, 如果小于 0, 则在逐个导出时每个模型使用各自的所有轨道动画时长最大值")]
|
||||||
|
public float Duration { get => Exporter.Duration; set => Exporter.Duration = value; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 帧率
|
||||||
|
/// </summary>
|
||||||
|
[Category("[1] 视频参数"), DisplayName("帧率"), Description("每秒画面数")]
|
||||||
|
public float FPS { get => Exporter.FPS; set => Exporter.FPS = value; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 保留最后一帧
|
||||||
|
/// </summary>
|
||||||
|
[Category("[1] 视频参数"), DisplayName("保留最后一帧"), Description("当设置保留最后一帧时, 动图会更为连贯, 但是帧数可能比预期帧数多 1")]
|
||||||
|
public bool KeepLast { get => Exporter.KeepLast; set => Exporter.KeepLast = value; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using SpineViewer.Exporter;
|
using FFMpegCore;
|
||||||
|
using SpineViewer.Utils;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
@@ -6,9 +7,49 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SpineViewer.PropertyGridWrappers.Exporter
|
namespace SpineViewer.Spine.SpineExporter
|
||||||
{
|
{
|
||||||
public class WebmExporterWrapper(WebmExporter exporter) : FFmpegVideoExporterWrapper(exporter)
|
/// <summary>
|
||||||
|
/// WebM 导出参数
|
||||||
|
/// </summary>
|
||||||
|
public class WebmExporter : FFmpegVideoExporter
|
||||||
|
{
|
||||||
|
public WebmExporter()
|
||||||
|
{
|
||||||
|
// 默认用透明黑背景
|
||||||
|
BackgroundColor = new(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Format => "webm";
|
||||||
|
|
||||||
|
public override string Suffix => ".webm";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 编码器
|
||||||
|
/// </summary>
|
||||||
|
public string Codec { get; set; } = "libvpx-vp9";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// CRF
|
||||||
|
/// </summary>
|
||||||
|
public int CRF { get => crf; set => crf = Math.Clamp(value, 0, 63); }
|
||||||
|
private int crf = 23;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 像素格式
|
||||||
|
/// </summary>
|
||||||
|
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)]
|
[Browsable(false)]
|
||||||
public override WebmExporter Exporter => (WebmExporter)base.Exporter;
|
public override WebmExporter Exporter => (WebmExporter)base.Exporter;
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using SpineViewer.Exporter;
|
using FFMpegCore;
|
||||||
|
using SpineViewer.Utils;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.ComponentModel;
|
using System.ComponentModel;
|
||||||
@@ -6,9 +7,59 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SpineViewer.PropertyGridWrappers.Exporter
|
namespace SpineViewer.Spine.SpineExporter
|
||||||
{
|
{
|
||||||
public class WebpExporterWrapper(WebpExporter exporter) : FFmpegVideoExporterWrapper(exporter)
|
/// <summary>
|
||||||
|
/// MP4 导出参数
|
||||||
|
/// </summary>
|
||||||
|
public class WebpExporter : FFmpegVideoExporter
|
||||||
|
{
|
||||||
|
public WebpExporter()
|
||||||
|
{
|
||||||
|
FPS = 24;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override string Format => "webp";
|
||||||
|
|
||||||
|
public override string Suffix => ".webp";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 编码器
|
||||||
|
/// </summary>
|
||||||
|
public string Codec { get; set; } = "libwebp_anim";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 是否无损
|
||||||
|
/// </summary>
|
||||||
|
public bool Lossless { get; set; } = false;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 质量
|
||||||
|
/// </summary>
|
||||||
|
public int Quality { get => quality; set => quality = Math.Clamp(value, 0, 100); }
|
||||||
|
private int quality = 75;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 像素格式
|
||||||
|
/// </summary>
|
||||||
|
public string PixelFormat { get; set; } = "yuva420p";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 循环次数, 0 无限循环, 取值范围 [0, 65535]
|
||||||
|
/// </summary>
|
||||||
|
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)]
|
[Browsable(false)]
|
||||||
public override WebpExporter Exporter => (WebpExporter)base.Exporter;
|
public override WebpExporter Exporter => (WebpExporter)base.Exporter;
|
||||||
@@ -1,5 +1,4 @@
|
|||||||
using SpineViewer.Exporter;
|
using System;
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ using System.Linq;
|
|||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace SpineViewer.PropertyGridWrappers
|
namespace SpineViewer.Utils
|
||||||
{
|
{
|
||||||
public class PointFConverter : ExpandableObjectConverter
|
public class PointFConverter : ExpandableObjectConverter
|
||||||
{
|
{
|
||||||
@@ -9,7 +9,7 @@ using System.Text;
|
|||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using System.Windows.Forms.Design;
|
using System.Windows.Forms.Design;
|
||||||
|
|
||||||
namespace SpineViewer.PropertyGridWrappers
|
namespace SpineViewer.Utils
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 使用 FolderBrowserDialog 的文件夹路径编辑器
|
/// 使用 FolderBrowserDialog 的文件夹路径编辑器
|
||||||
Reference in New Issue
Block a user