统一调用
This commit is contained in:
270
SpineViewer/Dialogs/ExportPngDialog.Designer.cs
generated
270
SpineViewer/Dialogs/ExportPngDialog.Designer.cs
generated
@@ -1,270 +0,0 @@
|
|||||||
namespace SpineViewer.Dialogs
|
|
||||||
{
|
|
||||||
partial class ExportPngDialog
|
|
||||||
{
|
|
||||||
/// <summary>
|
|
||||||
/// Required designer variable.
|
|
||||||
/// </summary>
|
|
||||||
private System.ComponentModel.IContainer components = null;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Clean up any resources being used.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
|
|
||||||
protected override void Dispose(bool disposing)
|
|
||||||
{
|
|
||||||
if (disposing && (components != null))
|
|
||||||
{
|
|
||||||
components.Dispose();
|
|
||||||
}
|
|
||||||
base.Dispose(disposing);
|
|
||||||
}
|
|
||||||
|
|
||||||
#region Windows Form Designer generated code
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Required method for Designer support - do not modify
|
|
||||||
/// the contents of this method with the code editor.
|
|
||||||
/// </summary>
|
|
||||||
private void InitializeComponent()
|
|
||||||
{
|
|
||||||
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ExportPngDialog));
|
|
||||||
panel1 = new Panel();
|
|
||||||
tableLayoutPanel1 = new TableLayoutPanel();
|
|
||||||
label4 = new Label();
|
|
||||||
label1 = new Label();
|
|
||||||
label2 = new Label();
|
|
||||||
label3 = new Label();
|
|
||||||
textBox_OutputDir = new TextBox();
|
|
||||||
button_SelectOutputDir = new Button();
|
|
||||||
tableLayoutPanel2 = new TableLayoutPanel();
|
|
||||||
button_Ok = new Button();
|
|
||||||
button_Cancel = new Button();
|
|
||||||
numericUpDown_Duration = new NumericUpDown();
|
|
||||||
numericUpDown_Fps = new NumericUpDown();
|
|
||||||
folderBrowserDialog = new FolderBrowserDialog();
|
|
||||||
panel1.SuspendLayout();
|
|
||||||
tableLayoutPanel1.SuspendLayout();
|
|
||||||
tableLayoutPanel2.SuspendLayout();
|
|
||||||
((System.ComponentModel.ISupportInitialize)numericUpDown_Duration).BeginInit();
|
|
||||||
((System.ComponentModel.ISupportInitialize)numericUpDown_Fps).BeginInit();
|
|
||||||
SuspendLayout();
|
|
||||||
//
|
|
||||||
// panel1
|
|
||||||
//
|
|
||||||
panel1.Controls.Add(tableLayoutPanel1);
|
|
||||||
panel1.Dock = DockStyle.Fill;
|
|
||||||
panel1.Location = new Point(0, 0);
|
|
||||||
panel1.Name = "panel1";
|
|
||||||
panel1.Padding = new Padding(50, 15, 50, 10);
|
|
||||||
panel1.Size = new Size(919, 276);
|
|
||||||
panel1.TabIndex = 1;
|
|
||||||
//
|
|
||||||
// tableLayoutPanel1
|
|
||||||
//
|
|
||||||
tableLayoutPanel1.AutoSize = true;
|
|
||||||
tableLayoutPanel1.ColumnCount = 4;
|
|
||||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle());
|
|
||||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
|
|
||||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
|
|
||||||
tableLayoutPanel1.ColumnStyles.Add(new ColumnStyle());
|
|
||||||
tableLayoutPanel1.Controls.Add(label4, 0, 0);
|
|
||||||
tableLayoutPanel1.Controls.Add(label1, 0, 1);
|
|
||||||
tableLayoutPanel1.Controls.Add(label2, 0, 2);
|
|
||||||
tableLayoutPanel1.Controls.Add(label3, 0, 3);
|
|
||||||
tableLayoutPanel1.Controls.Add(textBox_OutputDir, 1, 1);
|
|
||||||
tableLayoutPanel1.Controls.Add(button_SelectOutputDir, 3, 1);
|
|
||||||
tableLayoutPanel1.Controls.Add(tableLayoutPanel2, 0, 4);
|
|
||||||
tableLayoutPanel1.Controls.Add(numericUpDown_Duration, 1, 2);
|
|
||||||
tableLayoutPanel1.Controls.Add(numericUpDown_Fps, 1, 3);
|
|
||||||
tableLayoutPanel1.Dock = DockStyle.Fill;
|
|
||||||
tableLayoutPanel1.Location = new Point(50, 15);
|
|
||||||
tableLayoutPanel1.Name = "tableLayoutPanel1";
|
|
||||||
tableLayoutPanel1.RowCount = 5;
|
|
||||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
|
||||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
|
||||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
|
||||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
|
||||||
tableLayoutPanel1.RowStyles.Add(new RowStyle());
|
|
||||||
tableLayoutPanel1.Size = new Size(819, 251);
|
|
||||||
tableLayoutPanel1.TabIndex = 0;
|
|
||||||
//
|
|
||||||
// label4
|
|
||||||
//
|
|
||||||
label4.AutoSize = true;
|
|
||||||
tableLayoutPanel1.SetColumnSpan(label4, 4);
|
|
||||||
label4.Dock = DockStyle.Fill;
|
|
||||||
label4.Location = new Point(15, 15);
|
|
||||||
label4.Margin = new Padding(15);
|
|
||||||
label4.Name = "label4";
|
|
||||||
label4.Size = new Size(789, 24);
|
|
||||||
label4.TabIndex = 11;
|
|
||||||
label4.Text = "说明:时长不足一帧时仅导出第一帧";
|
|
||||||
label4.TextAlign = ContentAlignment.MiddleCenter;
|
|
||||||
//
|
|
||||||
// label1
|
|
||||||
//
|
|
||||||
label1.Anchor = AnchorStyles.Right;
|
|
||||||
label1.AutoSize = true;
|
|
||||||
label1.Location = new Point(3, 62);
|
|
||||||
label1.Name = "label1";
|
|
||||||
label1.Size = new Size(104, 24);
|
|
||||||
label1.TabIndex = 0;
|
|
||||||
label1.Text = "输出文件夹:";
|
|
||||||
//
|
|
||||||
// label2
|
|
||||||
//
|
|
||||||
label2.Anchor = AnchorStyles.Right;
|
|
||||||
label2.AutoSize = true;
|
|
||||||
label2.Location = new Point(57, 100);
|
|
||||||
label2.Name = "label2";
|
|
||||||
label2.Size = new Size(50, 24);
|
|
||||||
label2.TabIndex = 1;
|
|
||||||
label2.Text = "时长:";
|
|
||||||
//
|
|
||||||
// label3
|
|
||||||
//
|
|
||||||
label3.Anchor = AnchorStyles.Right;
|
|
||||||
label3.AutoSize = true;
|
|
||||||
label3.Location = new Point(57, 136);
|
|
||||||
label3.Name = "label3";
|
|
||||||
label3.Size = new Size(50, 24);
|
|
||||||
label3.TabIndex = 2;
|
|
||||||
label3.Text = "帧率:";
|
|
||||||
//
|
|
||||||
// textBox_OutputDir
|
|
||||||
//
|
|
||||||
tableLayoutPanel1.SetColumnSpan(textBox_OutputDir, 2);
|
|
||||||
textBox_OutputDir.Dock = DockStyle.Fill;
|
|
||||||
textBox_OutputDir.Location = new Point(113, 57);
|
|
||||||
textBox_OutputDir.Name = "textBox_OutputDir";
|
|
||||||
textBox_OutputDir.Size = new Size(664, 30);
|
|
||||||
textBox_OutputDir.TabIndex = 3;
|
|
||||||
//
|
|
||||||
// button_SelectOutputDir
|
|
||||||
//
|
|
||||||
button_SelectOutputDir.AutoSize = true;
|
|
||||||
button_SelectOutputDir.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
|
||||||
button_SelectOutputDir.Location = new Point(783, 57);
|
|
||||||
button_SelectOutputDir.Name = "button_SelectOutputDir";
|
|
||||||
button_SelectOutputDir.Size = new Size(32, 34);
|
|
||||||
button_SelectOutputDir.TabIndex = 5;
|
|
||||||
button_SelectOutputDir.Text = "...";
|
|
||||||
button_SelectOutputDir.UseVisualStyleBackColor = true;
|
|
||||||
button_SelectOutputDir.Click += button_SelectOutputDir_Click;
|
|
||||||
//
|
|
||||||
// tableLayoutPanel2
|
|
||||||
//
|
|
||||||
tableLayoutPanel2.AutoSize = true;
|
|
||||||
tableLayoutPanel2.AutoSizeMode = AutoSizeMode.GrowAndShrink;
|
|
||||||
tableLayoutPanel2.ColumnCount = 2;
|
|
||||||
tableLayoutPanel1.SetColumnSpan(tableLayoutPanel2, 4);
|
|
||||||
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
|
|
||||||
tableLayoutPanel2.ColumnStyles.Add(new ColumnStyle(SizeType.Percent, 50F));
|
|
||||||
tableLayoutPanel2.Controls.Add(button_Ok, 0, 0);
|
|
||||||
tableLayoutPanel2.Controls.Add(button_Cancel, 1, 0);
|
|
||||||
tableLayoutPanel2.Dock = DockStyle.Bottom;
|
|
||||||
tableLayoutPanel2.Location = new Point(3, 208);
|
|
||||||
tableLayoutPanel2.Name = "tableLayoutPanel2";
|
|
||||||
tableLayoutPanel2.RowCount = 1;
|
|
||||||
tableLayoutPanel2.RowStyles.Add(new RowStyle());
|
|
||||||
tableLayoutPanel2.Size = new Size(813, 40);
|
|
||||||
tableLayoutPanel2.TabIndex = 10;
|
|
||||||
//
|
|
||||||
// button_Ok
|
|
||||||
//
|
|
||||||
button_Ok.Anchor = AnchorStyles.Bottom | AnchorStyles.Right;
|
|
||||||
button_Ok.Location = new Point(264, 3);
|
|
||||||
button_Ok.Margin = new Padding(3, 3, 30, 3);
|
|
||||||
button_Ok.Name = "button_Ok";
|
|
||||||
button_Ok.Size = new Size(112, 34);
|
|
||||||
button_Ok.TabIndex = 7;
|
|
||||||
button_Ok.Text = "确认";
|
|
||||||
button_Ok.UseVisualStyleBackColor = true;
|
|
||||||
button_Ok.Click += button_Ok_Click;
|
|
||||||
//
|
|
||||||
// button_Cancel
|
|
||||||
//
|
|
||||||
button_Cancel.Anchor = AnchorStyles.Bottom | AnchorStyles.Left;
|
|
||||||
button_Cancel.Location = new Point(436, 3);
|
|
||||||
button_Cancel.Margin = new Padding(30, 3, 3, 3);
|
|
||||||
button_Cancel.Name = "button_Cancel";
|
|
||||||
button_Cancel.Size = new Size(112, 34);
|
|
||||||
button_Cancel.TabIndex = 8;
|
|
||||||
button_Cancel.Text = "取消";
|
|
||||||
button_Cancel.UseVisualStyleBackColor = true;
|
|
||||||
button_Cancel.Click += button_Cancel_Click;
|
|
||||||
//
|
|
||||||
// numericUpDown_Duration
|
|
||||||
//
|
|
||||||
numericUpDown_Duration.Anchor = AnchorStyles.Left;
|
|
||||||
numericUpDown_Duration.DecimalPlaces = 3;
|
|
||||||
numericUpDown_Duration.Location = new Point(113, 97);
|
|
||||||
numericUpDown_Duration.Maximum = new decimal(new int[] { 300, 0, 0, 0 });
|
|
||||||
numericUpDown_Duration.Name = "numericUpDown_Duration";
|
|
||||||
numericUpDown_Duration.Size = new Size(180, 30);
|
|
||||||
numericUpDown_Duration.TabIndex = 12;
|
|
||||||
numericUpDown_Duration.TextAlign = HorizontalAlignment.Right;
|
|
||||||
numericUpDown_Duration.Value = new decimal(new int[] { 1, 0, 0, 0 });
|
|
||||||
//
|
|
||||||
// numericUpDown_Fps
|
|
||||||
//
|
|
||||||
numericUpDown_Fps.Anchor = AnchorStyles.Left;
|
|
||||||
numericUpDown_Fps.Location = new Point(113, 133);
|
|
||||||
numericUpDown_Fps.Maximum = new decimal(new int[] { 300, 0, 0, 0 });
|
|
||||||
numericUpDown_Fps.Minimum = new decimal(new int[] { 1, 0, 0, 0 });
|
|
||||||
numericUpDown_Fps.Name = "numericUpDown_Fps";
|
|
||||||
numericUpDown_Fps.Size = new Size(180, 30);
|
|
||||||
numericUpDown_Fps.TabIndex = 13;
|
|
||||||
numericUpDown_Fps.TextAlign = HorizontalAlignment.Right;
|
|
||||||
numericUpDown_Fps.Value = new decimal(new int[] { 60, 0, 0, 0 });
|
|
||||||
//
|
|
||||||
// folderBrowserDialog
|
|
||||||
//
|
|
||||||
folderBrowserDialog.AddToRecent = false;
|
|
||||||
//
|
|
||||||
// ExportPngDialog
|
|
||||||
//
|
|
||||||
AcceptButton = button_Ok;
|
|
||||||
AutoScaleDimensions = new SizeF(11F, 24F);
|
|
||||||
AutoScaleMode = AutoScaleMode.Font;
|
|
||||||
CancelButton = button_Cancel;
|
|
||||||
ClientSize = new Size(919, 276);
|
|
||||||
Controls.Add(panel1);
|
|
||||||
FormBorderStyle = FormBorderStyle.FixedDialog;
|
|
||||||
Icon = (Icon)resources.GetObject("$this.Icon");
|
|
||||||
MaximizeBox = false;
|
|
||||||
MinimizeBox = false;
|
|
||||||
Name = "ExportPngDialog";
|
|
||||||
ShowInTaskbar = false;
|
|
||||||
StartPosition = FormStartPosition.CenterScreen;
|
|
||||||
Text = "导出PNG序列";
|
|
||||||
Load += ExportPngDialog_Load;
|
|
||||||
panel1.ResumeLayout(false);
|
|
||||||
panel1.PerformLayout();
|
|
||||||
tableLayoutPanel1.ResumeLayout(false);
|
|
||||||
tableLayoutPanel1.PerformLayout();
|
|
||||||
tableLayoutPanel2.ResumeLayout(false);
|
|
||||||
((System.ComponentModel.ISupportInitialize)numericUpDown_Duration).EndInit();
|
|
||||||
((System.ComponentModel.ISupportInitialize)numericUpDown_Fps).EndInit();
|
|
||||||
ResumeLayout(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
#endregion
|
|
||||||
|
|
||||||
private Panel panel1;
|
|
||||||
private TableLayoutPanel tableLayoutPanel1;
|
|
||||||
private Label label4;
|
|
||||||
private Label label1;
|
|
||||||
private Label label2;
|
|
||||||
private Label label3;
|
|
||||||
private TextBox textBox_OutputDir;
|
|
||||||
private Button button_SelectOutputDir;
|
|
||||||
private TableLayoutPanel tableLayoutPanel2;
|
|
||||||
private Button button_Ok;
|
|
||||||
private Button button_Cancel;
|
|
||||||
private NumericUpDown numericUpDown_Duration;
|
|
||||||
private NumericUpDown numericUpDown_Fps;
|
|
||||||
private FolderBrowserDialog folderBrowserDialog;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,78 +0,0 @@
|
|||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel;
|
|
||||||
using System.Data;
|
|
||||||
using System.Drawing;
|
|
||||||
using System.Linq;
|
|
||||||
using System.Text;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using System.Windows.Forms;
|
|
||||||
|
|
||||||
namespace SpineViewer.Dialogs
|
|
||||||
{
|
|
||||||
public partial class ExportPngDialog : Form
|
|
||||||
{
|
|
||||||
// TODO: 该对话框要合并到统一的导出参数对话框
|
|
||||||
// TODO: 使用结果包装类
|
|
||||||
public string OutputDir { get; private set; }
|
|
||||||
public float Duration { get; private set; }
|
|
||||||
public uint Fps { get; private set; }
|
|
||||||
|
|
||||||
public ExportPngDialog()
|
|
||||||
{
|
|
||||||
InitializeComponent();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ExportPngDialog_Load(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
button_SelectOutputDir_Click(sender, e);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void button_SelectOutputDir_Click(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
folderBrowserDialog.InitialDirectory = textBox_OutputDir.Text;
|
|
||||||
if (folderBrowserDialog.ShowDialog() == DialogResult.OK)
|
|
||||||
{
|
|
||||||
textBox_OutputDir.Text = Path.GetFullPath(folderBrowserDialog.SelectedPath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void button_Ok_Click(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
var outputDir = textBox_OutputDir.Text;
|
|
||||||
if (File.Exists(outputDir))
|
|
||||||
{
|
|
||||||
MessageBox.Info("输出文件夹无效");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!Directory.Exists(outputDir))
|
|
||||||
{
|
|
||||||
if (MessageBox.Quest($"文件夹 {outputDir} 不存在,是否创建?") != DialogResult.OK)
|
|
||||||
return;
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
Directory.CreateDirectory(outputDir);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Program.Logger.Error(ex.ToString());
|
|
||||||
MessageBox.Error(ex.ToString(), "文件夹创建失败");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
OutputDir = Path.GetFullPath(outputDir);
|
|
||||||
Duration = (float)numericUpDown_Duration.Value;
|
|
||||||
Fps = (uint)numericUpDown_Fps.Value;
|
|
||||||
|
|
||||||
DialogResult = DialogResult.OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void button_Cancel_Click(object sender, EventArgs e)
|
|
||||||
{
|
|
||||||
DialogResult = DialogResult.Cancel;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -67,21 +67,15 @@ namespace SpineViewer.Exporter
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 画面帧导出参数
|
/// 单帧画面导出参数
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class ExportFrameArgs : ExportArgs
|
public class FrameExportArgs : ExportArgs
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 名称后缀
|
/// 单帧画面格式
|
||||||
/// </summary>
|
|
||||||
[Category("画面帧"), DisplayName("名称后缀"), Description("逐个导出时必须提供该值,否则存在文件覆盖风险")]
|
|
||||||
public string NameSuffix { get; set; } = "(preview)";
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 画面帧格式
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TypeConverter(typeof(ImageFormatConverter))]
|
[TypeConverter(typeof(ImageFormatConverter))]
|
||||||
[Category("画面帧"), DisplayName("图像格式")]
|
[Category("单帧画面"), DisplayName("图像格式")]
|
||||||
public ImageFormat ImageFormat
|
public ImageFormat ImageFormat
|
||||||
{
|
{
|
||||||
get => imageFormat;
|
get => imageFormat;
|
||||||
@@ -96,14 +90,14 @@ namespace SpineViewer.Exporter
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 文件名后缀
|
/// 文件名后缀
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[Category("画面帧"), DisplayName("文件名后缀"), Description("与图像格式匹配的文件名后缀")]
|
[Category("单帧画面"), DisplayName("文件名后缀"), Description("与图像格式匹配的文件名后缀")]
|
||||||
public string FileSuffix { get => imageFormat.GetSuffix(); }
|
public string FileSuffix { get => imageFormat.GetSuffix(); }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 四周填充像素值
|
/// 四周填充像素值
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TypeConverter(typeof(PaddingConverter))]
|
[TypeConverter(typeof(PaddingConverter))]
|
||||||
[Category("画面帧"), DisplayName("四周填充像素值"), Description("在图内四周留出来的透明像素区域, 画面内容的可用范围是分辨率裁去填充区域")]
|
[Category("单帧画面"), DisplayName("四周填充像素值"), Description("在图内四周留出来的透明像素区域, 画面内容的可用范围是分辨率裁去填充区域")]
|
||||||
public Padding Padding
|
public Padding Padding
|
||||||
{
|
{
|
||||||
get => padding;
|
get => padding;
|
||||||
@@ -122,7 +116,7 @@ namespace SpineViewer.Exporter
|
|||||||
/// DPI
|
/// DPI
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[TypeConverter(typeof(SizeFConverter))]
|
[TypeConverter(typeof(SizeFConverter))]
|
||||||
[Category("画面帧"), DisplayName("DPI"), Description("导出图像的每英寸像素数,用于调整图像的物理尺寸")]
|
[Category("单帧画面"), DisplayName("DPI"), Description("导出图像的每英寸像素数,用于调整图像的物理尺寸")]
|
||||||
public SizeF DPI
|
public SizeF DPI
|
||||||
{
|
{
|
||||||
get => dpi;
|
get => dpi;
|
||||||
@@ -134,21 +128,45 @@ namespace SpineViewer.Exporter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
private SizeF dpi = new(144, 144);
|
private SizeF dpi = new(144, 144);
|
||||||
|
|
||||||
public override string? Validate()
|
|
||||||
{
|
|
||||||
if (base.Validate() is string error)
|
|
||||||
return error;
|
|
||||||
if (string.IsNullOrEmpty(OutputDir) && string.IsNullOrEmpty(NameSuffix))
|
|
||||||
return "输出文件夹和名称后缀不可同时为空,存在文件覆盖风险";
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 视频导出参数基类
|
/// 视频导出参数基类
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public abstract class ExportVideoArgs:ExportArgs
|
public abstract class VideoExportArgs : ExportArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 导出时长
|
||||||
|
/// </summary>
|
||||||
|
[Category("视频参数"), DisplayName("时长"), Description("可以从模型列表查看动画时长")]
|
||||||
|
public float Duration { get => duration; set => duration = Math.Max(0, value); }
|
||||||
|
private float duration = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 帧率
|
||||||
|
/// </summary>
|
||||||
|
[Category("视频参数"), DisplayName("帧率"), Description("每秒画面数")]
|
||||||
|
public float FPS { get; set; } = 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 帧序列导出参数
|
||||||
|
/// </summary>
|
||||||
|
public class FrameSequenceExportArgs : VideoExportArgs
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 文件名后缀
|
||||||
|
/// </summary>
|
||||||
|
[TypeConverter(typeof(SFMLImageFileSuffixConverter))]
|
||||||
|
[Category("帧序列参数"), DisplayName("文件名后缀"), Description("帧文件的后缀,同时决定帧图像格式")]
|
||||||
|
public string FileSuffix { get; set; } = ".png";
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// GIF 导出参数
|
||||||
|
/// </summary>
|
||||||
|
public class GifExportArgs : VideoExportArgs
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
287
SpineViewer/Exporter/Exporter.cs
Normal file
287
SpineViewer/Exporter/Exporter.cs
Normal file
@@ -0,0 +1,287 @@
|
|||||||
|
using SpineViewer.Spine;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SpineViewer.Exporter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 导出器基类
|
||||||
|
/// </summary>
|
||||||
|
public abstract class Exporter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 导出参数
|
||||||
|
/// </summary>
|
||||||
|
public required ExportArgs ExportArgs { get; init; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 根据参数获取渲染目标
|
||||||
|
/// </summary>
|
||||||
|
protected SFML.Graphics.RenderTexture GetRenderTexture()
|
||||||
|
{
|
||||||
|
var tex = new SFML.Graphics.RenderTexture((uint)ExportArgs.Resolution.Width, (uint)ExportArgs.Resolution.Height);
|
||||||
|
tex.Clear(SFML.Graphics.Color.Transparent);
|
||||||
|
tex.SetView(ExportArgs.View);
|
||||||
|
return tex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 得到需要渲染的模型数组,并按渲染顺序排列
|
||||||
|
/// </summary>
|
||||||
|
protected Spine.Spine[] GetSpinesToRender(IEnumerable<Spine.Spine> spines)
|
||||||
|
{
|
||||||
|
return spines.Where(sp => !ExportArgs.RenderSelectedOnly || sp.IsSelected).Reverse().ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 执行导出
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="spines">要进行导出的 Spine 列表</param>
|
||||||
|
/// <param name="worker">用来执行该函数的 worker</param>
|
||||||
|
public abstract void Export(IEnumerable<Spine.Spine> spines, BackgroundWorker? worker = null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 单帧画面导出器
|
||||||
|
/// </summary>
|
||||||
|
public class FrameExporter : Exporter
|
||||||
|
{
|
||||||
|
public override void Export(IEnumerable<Spine.Spine> spines, BackgroundWorker? worker = null)
|
||||||
|
{
|
||||||
|
var args = (FrameExportArgs)ExportArgs;
|
||||||
|
using var tex = GetRenderTexture();
|
||||||
|
var spinesToRender = GetSpinesToRender(spines);
|
||||||
|
var timestamp = DateTime.Now;
|
||||||
|
|
||||||
|
int total = spinesToRender.Length;
|
||||||
|
int success = 0;
|
||||||
|
int error = 0;
|
||||||
|
|
||||||
|
worker?.ReportProgress(0, $"已处理 0/{total}");
|
||||||
|
for (int i = 0; i < total; i++)
|
||||||
|
{
|
||||||
|
if (worker?.CancellationPending == true)
|
||||||
|
{
|
||||||
|
Program.Logger.Info("Export cancelled");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
var spine = spinesToRender[i];
|
||||||
|
tex.Draw(spine);
|
||||||
|
|
||||||
|
if (args.ExportSingle)
|
||||||
|
{
|
||||||
|
// 导出单个则直接算成功, 在最后一次将整体导出
|
||||||
|
success++;
|
||||||
|
if (i >= total - 1)
|
||||||
|
{
|
||||||
|
tex.Display();
|
||||||
|
|
||||||
|
// 导出单个时必定提供输出文件夹
|
||||||
|
var filename = $"frame_{timestamp:yyMMddHHmmss}{args.FileSuffix}";
|
||||||
|
var savePath = Path.Combine(args.OutputDir, filename);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var img = new Bitmap(tex.Texture.CopyToBitmap()))
|
||||||
|
{
|
||||||
|
img.SetResolution(args.DPI.Width, args.DPI.Height);
|
||||||
|
img.Save(savePath, args.ImageFormat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Program.Logger.Error(ex.ToString());
|
||||||
|
Program.Logger.Error("Failed to save single frame");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// 逐个导出则立即渲染, 并且保存完之后需要清除画面
|
||||||
|
tex.Display();
|
||||||
|
|
||||||
|
// 逐个导出时如果提供了输出文件夹, 则全部导出到输出文件夹, 否则输出到各自的文件夹
|
||||||
|
var filename = $"{spine.Name}_{timestamp:yyMMddHHmmss}{args.FileSuffix}";
|
||||||
|
var savePath = args.OutputDir is null ? Path.Combine(spine.AssetsDir, filename) : Path.Combine(args.OutputDir, filename);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using (var img = new Bitmap(tex.Texture.CopyToBitmap()))
|
||||||
|
{
|
||||||
|
img.SetResolution(args.DPI.Width, args.DPI.Height);
|
||||||
|
img.Save(savePath, args.ImageFormat);
|
||||||
|
}
|
||||||
|
success++;
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Program.Logger.Error(ex.ToString());
|
||||||
|
Program.Logger.Error("Failed to save frame {}", spine.SkelPath);
|
||||||
|
error++;
|
||||||
|
}
|
||||||
|
|
||||||
|
tex.Clear(SFML.Graphics.Color.Transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
worker?.ReportProgress((int)((i + 1) * 100.0) / total, $"已处理 {i + 1}/{total}");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 输出逐个导出的统计信息
|
||||||
|
if (!args.ExportSingle)
|
||||||
|
{
|
||||||
|
if (error > 0)
|
||||||
|
Program.Logger.Warn("Frames save {} successfully, {} failed", success, error);
|
||||||
|
else
|
||||||
|
Program.Logger.Info("{} frames saved successfully", success);
|
||||||
|
}
|
||||||
|
|
||||||
|
Program.LogCurrentMemoryUsage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 视频导出基类
|
||||||
|
/// </summary>
|
||||||
|
public abstract class VideoExporter : Exporter
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 生成单个模型的帧序列
|
||||||
|
/// </summary>
|
||||||
|
protected IEnumerable<SFMLImageVideoFrame> GetFrames(SFML.Graphics.RenderTexture tex, Spine.Spine spine, BackgroundWorker? worker = null)
|
||||||
|
{
|
||||||
|
var args = (VideoExportArgs)ExportArgs;
|
||||||
|
float delta = 1f / args.FPS;
|
||||||
|
int total = 1 + (int)(args.Duration * args.FPS); // 至少导出 1 帧
|
||||||
|
|
||||||
|
spine.CurrentAnimation = spine.CurrentAnimation;
|
||||||
|
worker?.ReportProgress(0, $"{spine.Name} 已处理 0/{total} 帧");
|
||||||
|
for (int i = 0; i < total; i++)
|
||||||
|
{
|
||||||
|
if (worker?.CancellationPending == true)
|
||||||
|
{
|
||||||
|
Program.Logger.Info("Export cancelled");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
tex.Clear(SFML.Graphics.Color.Transparent);
|
||||||
|
tex.Draw(spine);
|
||||||
|
spine.Update(delta);
|
||||||
|
tex.Display();
|
||||||
|
worker?.ReportProgress((int)((i + 1) * 100.0) / total, $"{spine.Name} 已处理 {i + 1}/{total} 帧");
|
||||||
|
yield return tex.Texture.CopyToFrame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 生成多个模型的帧序列
|
||||||
|
/// </summary>
|
||||||
|
protected IEnumerable<SFMLImageVideoFrame> GetFrames(SFML.Graphics.RenderTexture tex, Spine.Spine[] spines, BackgroundWorker? worker = null)
|
||||||
|
{
|
||||||
|
var args = (VideoExportArgs)ExportArgs;
|
||||||
|
float delta = 1f / args.FPS;
|
||||||
|
int total = 1 + (int)(args.Duration * args.FPS); // 至少导出 1 帧
|
||||||
|
|
||||||
|
foreach (var spine in spines) spine.CurrentAnimation = spine.CurrentAnimation;
|
||||||
|
worker?.ReportProgress(0, $"已处理 0/{total} 帧");
|
||||||
|
for (int i = 0; i < total; i++)
|
||||||
|
{
|
||||||
|
if (worker?.CancellationPending == true)
|
||||||
|
{
|
||||||
|
Program.Logger.Info("Export cancelled");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
tex.Clear(SFML.Graphics.Color.Transparent);
|
||||||
|
foreach (var spine in spines)
|
||||||
|
{
|
||||||
|
tex.Draw(spine);
|
||||||
|
spine.Update(delta);
|
||||||
|
}
|
||||||
|
tex.Display();
|
||||||
|
worker?.ReportProgress((int)((i + 1) * 100.0) / total, $"已处理 {i + 1}/{total} 帧");
|
||||||
|
yield return tex.Texture.CopyToFrame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 帧序列导出器
|
||||||
|
/// </summary>
|
||||||
|
public class FrameSequenceExporter : VideoExporter
|
||||||
|
{
|
||||||
|
public override void Export(IEnumerable<Spine.Spine> spines, BackgroundWorker? worker = null)
|
||||||
|
{
|
||||||
|
var args = (FrameSequenceExportArgs)ExportArgs;
|
||||||
|
using var tex = GetRenderTexture();
|
||||||
|
var spinesToRender = GetSpinesToRender(spines);
|
||||||
|
var timestamp = DateTime.Now;
|
||||||
|
|
||||||
|
if (args.ExportSingle)
|
||||||
|
{
|
||||||
|
int frameIdx = 0;
|
||||||
|
foreach (var frame in GetFrames(tex, spinesToRender, worker))
|
||||||
|
{
|
||||||
|
// 导出单个时必定提供输出文件夹
|
||||||
|
var filename = $"frames_{timestamp:yyMMddHHmmss}_{args.FPS:f0}_{frameIdx:d6}{args.FileSuffix}";
|
||||||
|
var savePath = Path.Combine(args.OutputDir, filename);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
frame.SaveToFile(savePath);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Program.Logger.Error(ex.ToString());
|
||||||
|
Program.Logger.Error("Failed to save frame {}", savePath);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
frame.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
frameIdx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
foreach (var spine in spinesToRender)
|
||||||
|
{
|
||||||
|
if (worker?.CancellationPending == true) break; // 取消的日志在 GetFrames 里输出
|
||||||
|
|
||||||
|
// 如果提供了输出文件夹, 则全部导出到输出文件夹, 否则导出到各自的文件夹下
|
||||||
|
var subDir = $"{spine.Name}_{timestamp:yyMMddHHmmss}_{args.FPS:f0}";
|
||||||
|
var saveDir = args.OutputDir is null ? Path.Combine(spine.AssetsDir, subDir) : Path.Combine(args.OutputDir, subDir);
|
||||||
|
Directory.CreateDirectory(saveDir);
|
||||||
|
|
||||||
|
int frameIdx = 0;
|
||||||
|
foreach (var frame in GetFrames(tex, spine, worker))
|
||||||
|
{
|
||||||
|
var filename = $"{spine.Name}_{timestamp:yyMMddHHmmss}_{args.FPS:f0}_{frameIdx:d6}{args.FileSuffix}";
|
||||||
|
var savePath = Path.Combine(saveDir, filename);
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
frame.SaveToFile(savePath);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
Program.Logger.Error(ex.ToString());
|
||||||
|
Program.Logger.Error("Failed to save frame {} {}", savePath, spine.SkelPath);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
frame.Dispose();
|
||||||
|
}
|
||||||
|
frameIdx++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Program.LogCurrentMemoryUsage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
SpineViewer/Exporter/TypeConverter.cs
Normal file
33
SpineViewer/Exporter/TypeConverter.cs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Globalization;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace SpineViewer.Exporter
|
||||||
|
{
|
||||||
|
public class SFMLImageFileSuffixConverter : StringConverter
|
||||||
|
{
|
||||||
|
private readonly string[] supportedFileSuffix = [".png", ".jpg", ".tga", ".bmp"];
|
||||||
|
public override bool GetStandardValuesSupported(ITypeDescriptorContext? context)
|
||||||
|
{
|
||||||
|
// 支持标准值列表
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool GetStandardValuesExclusive(ITypeDescriptorContext? context)
|
||||||
|
{
|
||||||
|
// 排他模式,只有下拉列表中的值可选
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override StandardValuesCollection? GetStandardValues(ITypeDescriptorContext? context)
|
||||||
|
{
|
||||||
|
return new StandardValuesCollection(supportedFileSuffix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
14
SpineViewer/MainForm.Designer.cs
generated
14
SpineViewer/MainForm.Designer.cs
generated
@@ -37,7 +37,7 @@
|
|||||||
toolStripSeparator1 = new ToolStripSeparator();
|
toolStripSeparator1 = new ToolStripSeparator();
|
||||||
toolStripMenuItem_Export = new ToolStripMenuItem();
|
toolStripMenuItem_Export = new ToolStripMenuItem();
|
||||||
toolStripMenuItem_ExportFrame = new ToolStripMenuItem();
|
toolStripMenuItem_ExportFrame = new ToolStripMenuItem();
|
||||||
toolStripMenuItem_ExportFrames = new ToolStripMenuItem();
|
toolStripMenuItem_ExportFrameSequence = new ToolStripMenuItem();
|
||||||
toolStripMenuItem_ExportGif = new ToolStripMenuItem();
|
toolStripMenuItem_ExportGif = new ToolStripMenuItem();
|
||||||
toolStripMenuItem_ExportMkv = new ToolStripMenuItem();
|
toolStripMenuItem_ExportMkv = new ToolStripMenuItem();
|
||||||
toolStripMenuItem_ExportMp4 = new ToolStripMenuItem();
|
toolStripMenuItem_ExportMp4 = new ToolStripMenuItem();
|
||||||
@@ -132,7 +132,7 @@
|
|||||||
//
|
//
|
||||||
// toolStripMenuItem_Export
|
// toolStripMenuItem_Export
|
||||||
//
|
//
|
||||||
toolStripMenuItem_Export.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_ExportFrame, toolStripMenuItem_ExportFrames, toolStripMenuItem_ExportGif, toolStripMenuItem_ExportMkv, toolStripMenuItem_ExportMp4, toolStripMenuItem_ExportMov, toolStripMenuItem_ExportWebm });
|
toolStripMenuItem_Export.DropDownItems.AddRange(new ToolStripItem[] { toolStripMenuItem_ExportFrame, toolStripMenuItem_ExportFrameSequence, toolStripMenuItem_ExportGif, toolStripMenuItem_ExportMkv, toolStripMenuItem_ExportMp4, toolStripMenuItem_ExportMov, toolStripMenuItem_ExportWebm });
|
||||||
toolStripMenuItem_Export.Name = "toolStripMenuItem_Export";
|
toolStripMenuItem_Export.Name = "toolStripMenuItem_Export";
|
||||||
toolStripMenuItem_Export.Size = new Size(270, 34);
|
toolStripMenuItem_Export.Size = new Size(270, 34);
|
||||||
toolStripMenuItem_Export.Text = "导出(&E)";
|
toolStripMenuItem_Export.Text = "导出(&E)";
|
||||||
@@ -146,10 +146,10 @@
|
|||||||
//
|
//
|
||||||
// toolStripMenuItem_ExportFrames
|
// toolStripMenuItem_ExportFrames
|
||||||
//
|
//
|
||||||
toolStripMenuItem_ExportFrames.Name = "toolStripMenuItem_ExportFrames";
|
toolStripMenuItem_ExportFrameSequence.Name = "toolStripMenuItem_ExportFrames";
|
||||||
toolStripMenuItem_ExportFrames.Size = new Size(270, 34);
|
toolStripMenuItem_ExportFrameSequence.Size = new Size(270, 34);
|
||||||
toolStripMenuItem_ExportFrames.Text = "帧序列...";
|
toolStripMenuItem_ExportFrameSequence.Text = "帧序列...";
|
||||||
toolStripMenuItem_ExportFrames.Click += toolStripMenuItem_ExportPng_Click;
|
toolStripMenuItem_ExportFrameSequence.Click += toolStripMenuItem_ExportFrameSequence_Click;
|
||||||
//
|
//
|
||||||
// toolStripMenuItem_ExportGif
|
// toolStripMenuItem_ExportGif
|
||||||
//
|
//
|
||||||
@@ -533,7 +533,7 @@
|
|||||||
private ToolStripMenuItem toolStripMenuItem_ConvertFileFormat;
|
private ToolStripMenuItem toolStripMenuItem_ConvertFileFormat;
|
||||||
private ToolStripMenuItem toolStripMenuItem_Export;
|
private ToolStripMenuItem toolStripMenuItem_Export;
|
||||||
private ToolStripMenuItem toolStripMenuItem_ExportFrame;
|
private ToolStripMenuItem toolStripMenuItem_ExportFrame;
|
||||||
private ToolStripMenuItem toolStripMenuItem_ExportFrames;
|
private ToolStripMenuItem toolStripMenuItem_ExportFrameSequence;
|
||||||
private ToolStripMenuItem toolStripMenuItem_ExportGif;
|
private ToolStripMenuItem toolStripMenuItem_ExportGif;
|
||||||
private ToolStripMenuItem toolStripMenuItem_ExportMp4;
|
private ToolStripMenuItem toolStripMenuItem_ExportMp4;
|
||||||
private ToolStripMenuItem toolStripMenuItem_ExportMov;
|
private ToolStripMenuItem toolStripMenuItem_ExportMov;
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ namespace SpineViewer
|
|||||||
|
|
||||||
var exportDialog = new Dialogs.ExportDialog()
|
var exportDialog = new Dialogs.ExportDialog()
|
||||||
{
|
{
|
||||||
ExportArgs = new ExportFrameArgs()
|
ExportArgs = new FrameExportArgs()
|
||||||
{
|
{
|
||||||
Resolution = spinePreviewer.Resolution,
|
Resolution = spinePreviewer.Resolution,
|
||||||
View = spinePreviewer.GetView(),
|
View = spinePreviewer.GetView(),
|
||||||
@@ -103,9 +103,8 @@ namespace SpineViewer
|
|||||||
progressDialog.ShowDialog();
|
progressDialog.ShowDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void toolStripMenuItem_ExportPng_Click(object sender, EventArgs e)
|
private void toolStripMenuItem_ExportFrameSequence_Click(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
// TODO: 改成统一导出调用
|
|
||||||
lock (spineListView.Spines)
|
lock (spineListView.Spines)
|
||||||
{
|
{
|
||||||
if (spineListView.Spines.Count <= 0)
|
if (spineListView.Spines.Count <= 0)
|
||||||
@@ -115,13 +114,21 @@ namespace SpineViewer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var exportDialog = new Dialogs.ExportPngDialog();
|
var exportDialog = new Dialogs.ExportDialog()
|
||||||
|
{
|
||||||
|
ExportArgs = new FrameSequenceExportArgs()
|
||||||
|
{
|
||||||
|
Resolution = spinePreviewer.Resolution,
|
||||||
|
View = spinePreviewer.GetView(),
|
||||||
|
RenderSelectedOnly = spinePreviewer.RenderSelectedOnly,
|
||||||
|
}
|
||||||
|
};
|
||||||
if (exportDialog.ShowDialog() != DialogResult.OK)
|
if (exportDialog.ShowDialog() != DialogResult.OK)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
var progressDialog = new Dialogs.ProgressDialog();
|
var progressDialog = new Dialogs.ProgressDialog();
|
||||||
progressDialog.DoWork += ExportPng_Work;
|
progressDialog.DoWork += ExportFrameSequence_Work;
|
||||||
progressDialog.RunWorkerAsync(exportDialog);
|
progressDialog.RunWorkerAsync(exportDialog.ExportArgs);
|
||||||
progressDialog.ShowDialog();
|
progressDialog.ShowDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -229,171 +236,24 @@ namespace SpineViewer
|
|||||||
propertyGrid_Spine.Refresh();
|
propertyGrid_Spine.Refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ExportPng_Work(object? sender, DoWorkEventArgs e)
|
|
||||||
{
|
|
||||||
var worker = sender as BackgroundWorker;
|
|
||||||
var arguments = e.Argument as Dialogs.ExportPngDialog;
|
|
||||||
var outputDir = arguments.OutputDir;
|
|
||||||
var duration = arguments.Duration;
|
|
||||||
var fps = arguments.Fps;
|
|
||||||
var timestamp = DateTime.Now.ToString("yyMMddHHmmss");
|
|
||||||
|
|
||||||
var renderSelectedOnly = spinePreviewer.RenderSelectedOnly;
|
|
||||||
|
|
||||||
var resolution = spinePreviewer.Resolution;
|
|
||||||
var tex = new SFML.Graphics.RenderTexture((uint)resolution.Width, (uint)resolution.Height);
|
|
||||||
tex.SetView(spinePreviewer.GetView());
|
|
||||||
var delta = 1f / fps;
|
|
||||||
var frameCount = 1 + (int)(duration / delta); // 零帧开始导出
|
|
||||||
|
|
||||||
spinePreviewer.StopRender();
|
|
||||||
|
|
||||||
lock (spineListView.Spines)
|
|
||||||
{
|
|
||||||
var spinesReverse = spineListView.Spines.Reverse();
|
|
||||||
|
|
||||||
// 重置动画时间
|
|
||||||
foreach (var spine in spinesReverse)
|
|
||||||
spine.CurrentAnimation = spine.CurrentAnimation;
|
|
||||||
|
|
||||||
Program.Logger.Info(
|
|
||||||
"Begin exporting png frames to output dir {}, duration: {}, fps: {}, totally {} spines",
|
|
||||||
[outputDir, duration, fps, spinesReverse.Count()]
|
|
||||||
);
|
|
||||||
|
|
||||||
// 逐帧导出
|
|
||||||
var success = 0;
|
|
||||||
worker.ReportProgress(0, $"已处理 0/{frameCount}");
|
|
||||||
for (int frameIndex = 0; frameIndex < frameCount; frameIndex++)
|
|
||||||
{
|
|
||||||
if (worker.CancellationPending)
|
|
||||||
break;
|
|
||||||
|
|
||||||
tex.Clear(SFML.Graphics.Color.Transparent);
|
|
||||||
|
|
||||||
foreach (var spine in spinesReverse)
|
|
||||||
{
|
|
||||||
if (renderSelectedOnly && !spine.IsSelected)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
tex.Draw(spine);
|
|
||||||
spine.Update(delta);
|
|
||||||
}
|
|
||||||
|
|
||||||
tex.Display();
|
|
||||||
using (var img = tex.Texture.CopyToImage())
|
|
||||||
{
|
|
||||||
img.SaveToFile(Path.Combine(outputDir, $"{timestamp}_{fps}_{frameIndex:d6}.png"));
|
|
||||||
}
|
|
||||||
|
|
||||||
success++;
|
|
||||||
worker.ReportProgress((int)((frameIndex + 1) * 100.0) / frameCount, $"已处理 {frameIndex + 1}/{frameCount}");
|
|
||||||
}
|
|
||||||
|
|
||||||
Program.Logger.Info("Exporting done: {}/{}", success, frameCount);
|
|
||||||
}
|
|
||||||
|
|
||||||
spinePreviewer.StartRender();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: 转移到 Exporter 里面
|
|
||||||
private void ExportFrame_Work(object? sender, DoWorkEventArgs e)
|
private void ExportFrame_Work(object? sender, DoWorkEventArgs e)
|
||||||
{
|
{
|
||||||
var worker = (BackgroundWorker)sender;
|
var worker = (BackgroundWorker)sender;
|
||||||
var args = (ExportFrameArgs)e.Argument;
|
var exporter = new FrameExporter() { ExportArgs = (ExportArgs)e.Argument };
|
||||||
|
|
||||||
using var tex = new SFML.Graphics.RenderTexture((uint)args.Resolution.Width, (uint)args.Resolution.Height);
|
|
||||||
tex.Clear(SFML.Graphics.Color.Transparent);
|
|
||||||
tex.SetView(args.View);
|
|
||||||
|
|
||||||
int success = 0;
|
|
||||||
int error = 0;
|
|
||||||
spinePreviewer.StopRender();
|
spinePreviewer.StopRender();
|
||||||
lock (spineListView.Spines)
|
lock (spineListView.Spines) { exporter.Export(spineListView.Spines, (BackgroundWorker)sender); }
|
||||||
{
|
e.Cancel = worker.CancellationPending;
|
||||||
// 根据是否仅渲染选中得到要渲染的模型数组
|
|
||||||
var spines = spineListView.Spines.Where(sp => !args.RenderSelectedOnly || sp.IsSelected).Reverse().ToArray();
|
|
||||||
|
|
||||||
int totalCount = spines.Length;
|
|
||||||
worker.ReportProgress(0, $"已处理 0/{totalCount}");
|
|
||||||
for (int i = 0; i < totalCount; i++)
|
|
||||||
{
|
|
||||||
if (worker.CancellationPending)
|
|
||||||
{
|
|
||||||
e.Cancel = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var spine = spines[i];
|
|
||||||
tex.Draw(spine);
|
|
||||||
|
|
||||||
if (args.ExportSingle)
|
|
||||||
{
|
|
||||||
// 导出单个则直接算成功
|
|
||||||
success++;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 逐个导出则立即渲染, 并且保存完之后需要清除画面
|
|
||||||
tex.Display();
|
|
||||||
|
|
||||||
var filename = $"{spine.Name}{args.NameSuffix}{args.FileSuffix}";
|
|
||||||
var savePath = args.OutputDir is null ? Path.Combine(spine.AssetsDir, filename) : Path.Combine(args.OutputDir, filename);
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using (var img = new Bitmap(tex.Texture.CopyToBitmap()))
|
|
||||||
{
|
|
||||||
img.SetResolution(args.DPI.Width, args.DPI.Height);
|
|
||||||
img.Save(savePath, args.ImageFormat);
|
|
||||||
}
|
|
||||||
success++;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Program.Logger.Error(ex.ToString());
|
|
||||||
Program.Logger.Error("Failed to save frame {}", spine.SkelPath);
|
|
||||||
error++;
|
|
||||||
}
|
|
||||||
|
|
||||||
tex.Clear(SFML.Graphics.Color.Transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
worker.ReportProgress((int)((i + 1) * 100.0) / totalCount, $"已处理 {i + 1}/{totalCount}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 导出单个
|
|
||||||
if (args.ExportSingle)
|
|
||||||
{
|
|
||||||
tex.Display();
|
|
||||||
|
|
||||||
var filename = $"{DateTime.Now:yyMMddHHmmss}{args.NameSuffix}{args.FileSuffix}";
|
|
||||||
var savePath = Path.Combine(args.OutputDir, filename);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
using (var img = new Bitmap(tex.Texture.CopyToBitmap()))
|
|
||||||
{
|
|
||||||
img.SetResolution(args.DPI.Width, args.DPI.Height);
|
|
||||||
img.Save(savePath, args.ImageFormat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Program.Logger.Error(ex.ToString());
|
|
||||||
Program.Logger.Error("Failed to save single frame");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (error > 0)
|
|
||||||
Program.Logger.Warn("Frames save {} successfully, {} failed", success, error);
|
|
||||||
else
|
|
||||||
Program.Logger.Info("{} frames saved successfully", success);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
spinePreviewer.StartRender();
|
spinePreviewer.StartRender();
|
||||||
|
}
|
||||||
|
|
||||||
Program.LogCurrentMemoryUsage();
|
private void ExportFrameSequence_Work(object? sender, DoWorkEventArgs e)
|
||||||
|
{
|
||||||
|
var worker = (BackgroundWorker)sender;
|
||||||
|
var exporter = new FrameSequenceExporter() { ExportArgs = (ExportArgs)e.Argument };
|
||||||
|
spinePreviewer.StopRender();
|
||||||
|
lock (spineListView.Spines) { exporter.Export(spineListView.Spines, (BackgroundWorker)sender); }
|
||||||
|
e.Cancel = worker.CancellationPending;
|
||||||
|
spinePreviewer.StartRender();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ConvertFileFormat_Work(object? sender, DoWorkEventArgs e)
|
private void ConvertFileFormat_Work(object? sender, DoWorkEventArgs e)
|
||||||
|
|||||||
Reference in New Issue
Block a user