目录
三、关键帧编辑
1、新建Winform工程
(1)界面布局
(2)全局变量
2、关键帧添加和删除
(1)鼠标在曲线上识别
(2)键盘按键按下捕捉
(3)关键帧添加、删除
(4)修改关键帧值
3、曲线插值
(1)三次样条插值
(2)工程代码下载链接
四、曲线数据导出和读取
1、数据导出
(1)添加导出按钮
(2)工程代码下载链接
2、读取导出的数据
(1)手动按钮创建曲线
(2)读取导出得关键帧数据
(3)工程代码下载链接
五、全部工程下载
1、基本功能
2、核心功能
接上一节《C#时间轴曲线图形编辑器开发1-基本功能》继续。
C#时间轴曲线图形编辑器开发1-基本功能_Big_潘大师的博客-CSDN博客
三、关键帧编辑
1、新建Winform工程
重新创建新的Winform工程测试
(1)界面布局
(2)全局变量
KeyDatasClass _keyData = new KeyDatasClass();
SplineEdit cureDraw;
float tensionSpline1 = 0.5f; //曲线1粗细
//曲线编辑
Dictionary<int, float> keyListDatas = new Dictionary<int, float>(); //关键帧集合
Color editSplineColor = Color.Blue; //曲线颜色
bool isMouseOnKeyPoint = false; //鼠标是否在关键点上检测
int dataLength = 0;
float[] myEditDatas; //绘制的曲线数据
int[] KeyDatasflag; //当前曲线数据是否是关键帧数据标值位
float[] keyEditDatas_X; //关键帧-方框点绘制-X轴数据
float[] keyEditDatas_Y; //关键帧-方框点绘制-X轴数据
bool isDataEdit = false; //关键帧是否要编辑
bool isKeyEditDataMoveCan = false;
int keyEditFrame = 0; //当前要编辑的关键帧数据
//鼠标当前在画图面板上的像素坐标,对应的坐标轴数值
bool isMouseOnSpline; //鼠标在曲线上检测
int currentValue_X;
float currentValue_Y;
int last_CurrentValueX = 0;
int ex = 0, ey = 0; //鼠标的坐标值
bool isLeftButtonDowm = false, isMiddleButtonDown = false, isRightButtonDown = false;
bool isCtrlKeyDown = false;
bool isShiftDown = false;
2、关键帧添加和删除
当检测鼠标在曲线上的时候,通过键盘和鼠标的组合按键操作,可以进行关键帧添加和删除操作。
(1)鼠标在曲线上识别
重新整理曲线识别代码,将其代码封装。将封装好的代码放在timer1_Tick中执行
/// <summary>
/// 检测鼠标是否在绘制得曲线上
/// </summary>
private void MouseOnSplineCheck()
{
//
if (currentValue_X < dataLength)
{
int nIndex = currentValue_X;
if (nIndex >= myEditDatas.Length)
{
nIndex = myEditDatas.Length - 1;
if (nIndex < 0)
{
nIndex = 0;
}
}
float selectValue_Y = myEditDatas[nIndex];
if (Math.Abs(selectValue_Y - currentValue_Y) < cureDraw.YSliceValue / 8)
{
isMouseOnSpline = true;
labMousePos.ForeColor = editSplineColor;
}
else
{
isMouseOnSpline = false;
labMousePos.ForeColor = Color.Black;
}
}
else
{
labMousePos.ForeColor = Color.Black;
}
}
(2)键盘按键按下捕捉
①在窗口属性中找到KeyPreview设置为True
②在窗口事件程序中分别添加KeyDowm、KeyUp事件函数
MainForm_KeyDown():
private void MainForm_KeyDown(object sender, KeyEventArgs e)
{
Keys k = e.KeyCode;
if (k == Keys.ControlKey)
{
isCtrlKeyDown = true;
}
if (k == Keys.ShiftKey)
{
isShiftDown = true;
}
}
MainForm_KeyUp():
private void MainForm_KeyUp(object sender, KeyEventArgs e)
{
Keys k = e.KeyCode;
if (k == Keys.ControlKey)
{
isCtrlKeyDown = false;
}
if (k == Keys.ShiftKey)
{
isShiftDown = false;
}
}
(3)关键帧添加、删除
①关键帧添加
鼠标在曲线上的时候,Ctrl键+鼠标左键按下,添加关键帧
②关键帧删除
鼠标在曲线上的时候,Shift键+鼠标左键按下,删除当前关键帧
③添加pictureBox1控件的单击事件函数:pictureBox1_Click
private void pictureBox1_Click(object sender, EventArgs e)
{
//添加关键帧数据
if(isMouseOnSpline && isCtrlKeyDown)
{
if (currentValue_X > dataLength)
{
MessageBox.Show("数据超过极限范围");
return;
}
//判断关键帧是否可以添加,判断当前要添加的关键帧是否已经存在
bool isKeyListCanAdd = false;
for (int i = 0; i < keyEditDatas_X.Length; i++)
{
if(currentValue_X!=keyEditDatas_X[i])
{
isKeyListCanAdd = true;
}
}
for (int i = 0; i < keyEditDatas_X.Length; i++)
{
if (currentValue_X == keyEditDatas_X[i])
{
isKeyListCanAdd = false;
}
}
if (isKeyListCanAdd)
{
//keyListDatas.Add(currentValue_X, currentValue_Y); //关键帧值设置为当前位置值
keyListDatas.Add(currentValue_X, 0.0f); //关键帧值设置为0
}
}
//删除当前关键帧
if (isKeyEditDataMoveCan && isShiftDown)
{
keyListDatas.Remove(keyEditFrame); //删除关键帧数据
myEditDatas[keyEditFrame] = 0.0f; //将曲线数据值修改为0
}
}
(4)修改关键帧值
修改关键帧值,除了鼠标拖动外,还要需要可以手动输入操作,这样可以精确的给定关键帧值。
实现方法,鼠标在关键帧的时候右键双击,弹出输入对话框。
①新建窗口
打开该窗口读取当前关键帧值、点击确定或者按回车将输入的值传到关键帧上。
窗口需要设置键盘识别
添加键盘事件:KeyDataSetForm_KeyDown
判断Enter键按下
private void KeyDataSetForm_KeyDown(object sender, KeyEventArgs e)
{
Keys k = e.KeyCode;
if (k == Keys.Enter)
{
_Son.KeyDataSet = float.Parse(txtKeyDataSet.Text);
this.Close();
}
if (k == Keys.Escape)
{
this.Close();
}
}
使用构造函数的方法进行窗口传值。(操作方法见工程代码)
②pictureBox1控件添加双击事件:pictureBox1_DoubleClick
private void pictureBox1_DoubleClick(object sender, EventArgs e)
{
if (isRightButtonDown && (label7.BackColor == Color.Lime))
{
//MessageBox.Show("123");
KeyDataSetForm kdf = new KeyDataSetForm(this, _keyData);
_keyData.KeyDataSet = currentValue_Y;
kdf.ShowDialog();
keyListDatas[keyEditFrame] = _keyData.KeyDataSet;
}
}
3、曲线插值
插值的目的是,根据当前关键帧的值得到一条完整的连续、圆滑曲线。这时就需要用到插值法,来计算曲线数组每个元素的值。
(1)三次样条插值
插值代码见程序工程。方法是,首先要将关键帧数据进行排序,然后再插值计算。
修改KeyDataTrans()代码,添加三次样条插值
/// <summary>
/// 关键帧数据集合转数组
/// </summary>
private void KeyDataTrans()
{
//关键点得数据值,对应到曲线数据上
foreach (KeyValuePair<int, float> item in keyListDatas)
{
int key = item.Key;
float fValue = item.Value;
//myEditDatas[key] = fValue;
KeyDatasflag[key] = 1; //关键帧所在的地方设置为1
}
//
List<float> fListTemp_X = new List<float> { };
List<float> fListTemp_Y = new List<float> { };
foreach (KeyValuePair<int, float> item in keyListDatas)
{
int key = item.Key;
float fValue = item.Value;
fListTemp_X.Add(key);
fListTemp_Y.Add(fValue);
}
keyEditDatas_X = fListTemp_X.ToArray();
keyEditDatas_Y = fListTemp_Y.ToArray();
//三次样条插值
PointClass[] points = new PointClass[keyEditDatas_X.Length];
for (int i = 0; i < keyEditDatas_X.Length; i++)
{
points[i] = new PointClass();
points[i].x = keyEditDatas_X[i];
points[i].y = keyEditDatas_Y[i];
}
PointClass.DeSortX(points); //排序
try
{
double[] xs = new double[myEditDatas.Length];
for (int i = 0; i < myEditDatas.Length; i++)
{
xs[i] = i;
}
double[] Y = DataInterpolation.SplineInsertPoint(points, xs, 1);
if (Y.Length == myEditDatas.Length)
{
for (int i = 0; i < myEditDatas.Length; i++)
{
myEditDatas[i] = (float)Y[i];
}
}
}
catch { }
}
未使用插值的曲线
使用三次样条插值后曲线
使用三次样条插值后操作演示
(2)工程代码下载链接
代码仅个人使用
链接:https://pan.baidu.com/s/1WLov3JLowmw_YS_wQT7QNw
提取码:fi2f
--来自百度网盘超级会员V4的分享
四、曲线数据导出和读取
1、数据导出
导出数据要求:数据分三列,第一列是行号、第二列是曲线数据、第三列是关键帧标志位。
(1)添加导出按钮
btnSplineDataExport_Click():
private void btnSplineDataExport_Click(object sender, EventArgs e)
{
if (myEditDatas.Length < 1)
{
MessageBox.Show("请先添加数据", "提示");
return;
}
//
string[] newLines = new string[myEditDatas.Length];
for (int i = 0; i < myEditDatas.Length; i++)
{
if (KeyDatasflag[i] == 1)
{
newLines[i] = "L" + i.ToString() + "\t" + myEditDatas[i].ToString("0.00") + "\t" + "1";
}
else
{
newLines[i] = "L" + i.ToString() + "\t" + myEditDatas[i].ToString("0.00") + "\t" + "0";
}
//newLines[i] = myEditDatas[i].ToString("0.00");
}
string path = @"C:\Users\Administrator\Desktop\TestData.txt";
using (System.IO.StreamWriter file = new System.IO.StreamWriter(path, true))
{
foreach (string line in newLines)
{
file.WriteLine(line);// 直接追加文件末尾,换行
//file.Write(line);//直接追加文件末尾,不换行
}
}
MessageBox.Show("导出完成", "提示");
}
曲线编辑和数据导出
(2)工程代码下载链接
代码仅个人使用
链接:https://pan.baidu.com/s/1Gpbe-fJGOqLdj7VxFTDdcg
提取码:vyyq
--来自百度网盘超级会员V4的分享
2、读取导出的数据
(1)手动按钮创建曲线
将窗口登陆时添加的关键帧数据,改成手动点击按钮生成。
btnDataCreate_Click()
private void btnDataCreate_Click(object sender, EventArgs e)
{
//曲线编辑器数据
dataLength = int.Parse(txtDataLength.Text);
myEditDatas = new float[dataLength];
KeyDatasflag = new int[myEditDatas.Length];
//字典集合中添加4个随机数据
keyListDatas.Add(100 + dataLength / 2, 40.0f);
keyListDatas.Add(0, 0.0f);
keyListDatas.Add(dataLength / 2, 10.0f);
keyListDatas.Add(dataLength - 1, 0.0f);
keyListDatas[dataLength / 2] = 50.0f; //修改字典集合中的值
KeyDataTrans();
}
修改MainForm_Load()
private void MainForm_Load(object sender, EventArgs e)
{
//
timer1.Start();
timer2.Start();
cureDraw = new SplineEdit(pictureBox1.Height, pictureBox1.Width);
DrawCure();
}
修改DrawCure()
private void DrawCure()
{
if (pictureBox1.Height > 0 && pictureBox1.Width > 0) //若窗口最小化时候,则Height、Width都为0。DrawImage()创建图像会出错
{
cureDraw.Height = pictureBox1.Height;
cureDraw.Width = pictureBox1.Width;
}
pictureBox1.Image = cureDraw.DrawImage();
//当前帧指示线
cureDraw.DrawCurrentLine(last_CurrentValueX);
if (isLeftButtonDowm)
{
last_CurrentValueX = currentValue_X;
if (myEditDatas != null)
{
if (last_CurrentValueX > myEditDatas.Length)
{
last_CurrentValueX = myEditDatas.Length;
}
}
}
//曲线编辑器
if (keyListDatas.Count > 0)
//if (keyEditDatas_Y.Length > 0) //绘制关键帧
{
cureDraw.DrawPoint(keyEditDatas_X, keyEditDatas_Y, editSplineColor, tensionSpline1, true);
}
if (dataLength > 0)
//if (myEditDatas.Length > 0) //绘制曲线
{
cureDraw.DrawSpline(myEditDatas, editSplineColor, 0.5f, false);
}
}
(2)读取导出得关键帧数据
btnSplineDataRead():
private void btnSplineDataRead_Click(object sender, EventArgs e)
{
try
{
OpenFileDialog ofd = new OpenFileDialog();
ofd.Filter = "All files(*.*)|*.*|文本文件(*.csv)|*.csv|文本文件(*.txt)|*.txt";
if (ofd.ShowDialog() != DialogResult.OK)
{
return;
}
int dataColumNums = 0; //动作文件列表数
int dataLineNums; //
double[,] axisDataArrayRead;
float[] axis1Datas;
string[] lines = File.ReadAllLines(ofd.FileName, Encoding.Default).ToArray();
dataColumNums = CharNum(lines[0], "\t");
dataLineNums = lines.Length;
axisDataArrayRead = new double[dataColumNums, dataLineNums];
axis1Datas = new float[dataLineNums];
//解析数据
for (int i = 0; i < lines.Length; i++)
{
string[] seg = lines[i].Split('\t'); //英文逗号分隔符
for (int j = 1; j < dataColumNums; j++) //
{
axisDataArrayRead[j, i] = Convert.ToDouble(seg[j]);
}
}
//轴1数据
for (int i = 0; i < lines.Length; i++)
{
axis1Datas[i] = (float)axisDataArrayRead[1, i];
}
MessageBox.Show("读取完毕,总共 " + dataLineNums.ToString() + " 行", "提示");
//
dataLength = dataLineNums;
myEditDatas = new float[dataLength];
KeyDatasflag = new int[myEditDatas.Length];
for (int i = 0; i < dataLength; i++)
{
myEditDatas[i] = (float)axisDataArrayRead[1, i];
KeyDatasflag[i] = (int)axisDataArrayRead[2, i];
if (KeyDatasflag[i] == 1)
{
keyListDatas.Add(i, myEditDatas[i]);
}
}
}
catch
{
MessageBox.Show("文件解析异常", "提示");
}
}
(3)工程代码下载链接
3
链接:https://pan.baidu.com/s/1uj4xVkTCpGVTarcqiJCxGA
提取码:w1o9
--来自百度网盘超级会员V4的分享
4
链接:https://pan.baidu.com/s/1Ly3mKv2WZ9mfNCvjKAIDPA
提取码:zydp
--来自百度网盘超级会员V4的分享
五、全部工程下载
1、基本功能
https://download.csdn.net/download/panjinliang066333/88112693
2、核心功能
https://download.csdn.net/download/panjinliang066333/88117382