C#时间轴曲线图形编辑器开发2-核心功能实现

news2024/11/18 4:01:08

目录

三、关键帧编辑

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/803131.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

脉冲信号测试应如何选择示波器带宽?

示波器模拟带宽的定义大家都比较熟悉&#xff0c;是针对于正弦波信号定义的。从频域上看&#xff0c;正弦波信号的频谱就是单根谱线&#xff0c;只要示波器的带宽不小于信号的频率&#xff0c;那么就可以有效观测到波形。若要追求更高的幅度测试精度&#xff0c;则可以按照5倍法…

全球视频编码领域顶级大赛放榜,网易云信首次参赛即斩获H.265赛道多项指标第一

近日&#xff0c;2022 MSU 世界视频编码器大赛正式放榜&#xff0c;网易云信首次参赛就获得骄人成绩&#xff0c;自研的结合智码超清技术的 NE265E 编码器以公开身份参赛&#xff0c;在 H.265 赛道下获得多项指标第一名。 首次参赛&#xff0c;斩获 3 项指标第一 MSU Video Co…

【JVM】详细解析java创建对象的具体流程

目录 一、java创建对象的几种方式 1.1、使用new关键字 1.2、反射创建对象 1.2.1、Class.newInstance创建对象 1.2.2、调用构造器再去创建对象Constructor.newInstance 1.3、clone实现 1.4、反序列化 二、创建对象的过程 2.1、分配空间的方式 1、指针碰撞 2、空闲列表 …

Mac代码编辑器sublime text 4中文注册版下载

Sublime Text 4 for Mac简单实用功能强大&#xff0c;是程序员敲代码必备的代码编辑器&#xff0c;sublime text 4中文注册版支持多种编程语言&#xff0c;包括C、Java、Python、Ruby等&#xff0c;可以帮助程序员快速编写代码。Sublime Text的界面简洁、美观&#xff0c;支持多…

数据库访问和组件技术相关概念(ADO、ActiveX、DLL、ODBC等)详解

目录 背景概念ADO核心组件代码展示 ActiveX组件对象模型ADO与ODBC的关系 总结 背景 最近又再重新学习vb&#xff0c;老师说过无论学习什么知识一定不能独立的学习&#xff0c;学习编程语言也是一样&#xff0c;把两种或者三种语言放到一起进行比较&#xff0c;通过比较每种语言…

短视频矩阵源码/系统搭建/源码

一、短视频矩阵系统开发需要具备以下能力 短视频技术能力&#xff1a;开发人员应具备短视频相关技术能力&#xff0c;如视频编解码、视频流媒体传输等。 大数据存储和处理能力&#xff1a;短视频矩阵系统需要处理大量的视频数据&#xff0c;因此需要具备大数据存储和处理的能力…

【Spring Boot】

目录 &#x1f36a;1 Spring Boot 的创建 &#x1f382;2 简单 Spring Boot 程序 &#x1f370;3 Spring Boot 配置文件 &#x1f36e;3.1 properties 基本语法 &#x1fad6;3.2 yml 配置文件说明 &#x1f36d;3.2.1 yml 基本语法 &#x1f369;3.3 配置文件里的配置类…

CSDN浏览如何解决

一、对于平时我们苦恼csdn数据不够好看 当面试等各个场合需要我们装*或者秀技术无法拿出亮眼的时候&#xff0c;刚好我闲时间编译的在线模块适合你 二、如何操作&#xff08;虚拟平台我已给大家放到最后直接使用即可&#xff09; 重点&#xff1a;pc端必须拥有python环境 win…

【lesson6】Linux make和makefile

文章目录 make和Makefile的介绍make和Makefile的使用make和Makefile的项目测试 make和Makefile的介绍 make是一个命令 Makefile是一个文件 make和Makefile是Linux自动化构建项目的工具。 makefile带来的好处就是——“自动化编译”&#xff0c;一旦写好&#xff0c;只需要一个…

6门新兴语言,小众亦强大

编码语言在塑造我们创建软件的方式方面起着至关重要的作用。多年来&#xff0c;我们观察到Python&#xff0c;Java和C等成熟语言的流行。然而&#xff0c;如今一波新的编码语言浪潮已经出现&#xff0c;提出了创造性的解决方案&#xff0c;并推动了软件工程领域所能完成的极限。…

12页线性代数图解教程,github星标9.1k,适合小白

线性代数“困难户”注意&#xff0c;今天我给大家分享一个超适合小白的线性代数学习笔记&#xff0c;只有12页纸&#xff0c;一半都是图解&#xff0c;不用担心看不懂。 这份笔记名为《线性代数的艺术》&#xff0c;是日本学者Kenji Hiranabe基于Gilbert Strang教授的《每个人…

Vue 组件传参 prop/emit

学习了组件用法&#xff0c;就像一种嵌套引用关系&#xff0c;在这个关系中&#xff0c;经常会涉及相互传数据的需求&#xff0c;即父组件传子组件&#xff0c;子组件传父组件。 父、子组件的关系可以总结为&#xff1a; prop 向下传递&#xff08;父传子&#xff09;&#xf…

Docker复习

目录 1. Docker的理解1.1 Docker三要素 2 安装Docker2.1 安装命令2.2 配置阿里云加速器 3 Docker命令3.1 启动类命令3.2 镜像类命令 4 实战4.1 启动容器&#xff0c;自动创建实例4.2 查看Docker内启动的容器4.3 退出容器4.4 其他4.5 导入导出文件4.6 commit 5 Dockerfile5.1 理…

MySQL数据库局域网连接

目录 前言 客户端 开启访问权限 防火墙设置 windows Linux 测试连接 前言 MySQL数据库远程连接&#xff08;局域网&#xff09;是指在局域网内的不同计算机或设备之间&#xff0c;通过网络连接到MySQL数据库服务器的过程。在这种情况下&#xff0c;MySQL数据库服务器位…

编译原理

一&#xff0c;基本概念 二&#xff0c;词法分析

使用python部署chineseocr_lite

使用python部署chineseocr_lite 简介安装报错解决python调用结果 简介 项目地址&#xff1a;https://github.com/DayBreak-u/chineseocr_lite chineseocr_lite 是一个开源项目&#xff0c;用来实现中文的文字识别&#xff0c;支持竖排文字识别、繁体识别&#xff0c;总模型只…

Mysql加锁过程

1、背景 MySQL/InnoDB的加锁分析&#xff0c;一直是一个比较困难的话题。我在工作过程中&#xff0c;经常会有同事咨询这方面的问题。同时&#xff0c;微博上也经常会收到MySQL锁相关的私信&#xff0c;让我帮助解决一些死锁的问题。本文&#xff0c;准备就MySQL/InnoDB的加锁问…

最优除法(力扣)数学 JAVA

给定一正整数数组 nums&#xff0c;nums 中的相邻整数将进行浮点除法。例如&#xff0c; [2,3,4] -> 2 / 3 / 4 。 例如&#xff0c;nums [2,3,4]&#xff0c;我们将求表达式的值 “2/3/4”。 但是&#xff0c;你可以在任意位置添加任意数目的括号&#xff0c;来改变算数的…

LT2911R -D 是一款MIPI/TTL和LVDS互转的一款高性能旋转芯片

LT2911R -D 1.概述&#xff1a;LT2911R-D是一种在MIPI/TTL/LVDS之间相互转换的高性能转换器&#xff0c;除了LVDS转MIPI除外。LT2911R-D反序列化输入的MIPI/LVDS/TTL视频数据&#xff0c;解码数据包、旋转视频、改变帧率&#xff0c;并将格式化的视频数据流转换为AP和移动显示…

给你推荐几个好用的Android Studio插件

给你推荐几个好用的Android Studio插件 1. CodeGlance&#xff08;代码全景&#xff09; 类似于Sublime或Xcode&#xff0c;此插件在您的编辑器中嵌入了一个代码迷你地图。滚动条也会变得稍大一些。CodeGlance预览了文件的代码模式&#xff0c;让您快速导航到所需的部分。 …