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

目录

三、关键帧编辑

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

全面适配 | 走近openGauss数据库+鲲鹏欧拉操作系统

引入 全面适配 | openEuler操作系统 openGauss数据库 开篇 1、openEuler欧拉操作系统 百度百科&#xff1a;openEuler是覆盖全场景的创新平台&#xff0c;在引领内核创新&#xff0c;夯实云化基座的基础上&#xff0c;面向计算架构互联总线、存储介质发展新趋势&#xff0c;…

某渣渣平台APP登录

准备 APP有壳----360的好像是&#xff0c;懒得回头再看了加密参数sign、password 过程就略过吧&#xff01;此处只展示结果

win10 hadoop报错 unable to load native-hadoop library

win10 安装hadoop执行hdfs -namenode format 和运行hadoop的start-all报错 unable to load native-hadoop library 验证&#xff1a; hadoop checknative -a 这个命令返回都是false是错的 返回下图是正确的 winutils: true D:\soft\hadoop-3.0.0\bin\winutils.exe Native li…

STM32MP157驱动开发——按键驱动(定时器)

内核函数 定时器涉及函数参考内核源码&#xff1a;include\linux\timer.h 给定时器的各个参数赋值&#xff1a; setup_timer(struct timer_list * timer, void (*function)(unsigned long),unsigned long data)&#xff1a;设置定时器&#xff1a;主要是初始化 timer_list 结…

CentOS7系统Nvidia Docker容器基于TensorFlow2.12测试GPU

CentOS7系统Nvidia Docker容器基于TensorFlow1.15测试GPU 参考我的另一篇博客 1. 安装NVIDIA-Docker的Tensorflow2.12.0版本 1. 版本依赖对应关系&#xff1a;从源代码构建 | TensorFlow GPU 版本Python 版本编译器构建工具cuDNNCUDAtensorflow-2.6.03.6-3.9GCC 7.3.1Ba…

F5 LTM 知识点和实验 4-持久化

第四章:持久化 持久化: 大多数应用都是有状态的,比如,使用一个购物网站,最重要的是用户在放入一个商品之后,刷新网页要能继续看到购物车里的东西,这就需要请求报文发到同一个后端服务器上,持久化就能完成这个功能。 持久化支持一下几种场景: 源地址目标地址SSLSIPH…

最后的组合:K8s 1.24 基于 Hekiti 实现 GlusterFS 动态存储管理实践

前言 知识点 定级&#xff1a;入门级GlusterFS 和 Heketi 简介GlusterFS 安装部署Heketi 安装部署Kubernetes 命令行对接 GlusterFS 实战服务器配置(架构 1:1 复刻小规模生产环境&#xff0c;配置略有不同) 主机名IPCPU内存系统盘数据盘用途ks-master-0192.168.9.912450100…

基于Open3D的点云处理0-测试所用数据下载

地址&#xff1a;github 20220201-data 20220301-data

Flutter 使用texture_rgba_renderer实现桌面端渲染视频

Flutter视频渲染系列 第一章 Android使用Texture渲染视频 第二章 Windows使用Texture渲染视频 第三章 Linux使用Texture渲染视频 第四章 全平台FFICustomPainter渲染视频 第五章 Windows使用Native窗口渲染视频 第六章 桌面端使用texture_rgba_renderer渲染视频&#xff08;本…

认识什么是架构

目录 ​编辑 一、架构是什么 1.1 系统与子系统 1.1.1 系统 1.1.1.1 关联 1.1.1.2 规则 1.1.1.3 能力 1.1.2 子系统 1.2 模块与组件 1.2.1 模块 1.2.2 组件 1.3 框架与架构 1.3.1 框架 1.3.2 架构 1.3.2.1 架构定义 1.3.2.2 架构组成 1.3.2.2.1 要素 1.3.2.2.2 结构 1.3.2…

苹果电脑系统优化工具:Ventura Cache Cleaner for mac

Ventura Cache Cleaner for Mac是一款专门为苹果电脑开发的系统优化工具&#xff0c;旨在帮助用户清理和优化Mac电脑&#xff0c;提高系统性能和速度。该软件由美国公司Northern Softworks开发&#xff0c;已经推出了多个版本&#xff0c;适用于不同版本的Mac操作系统。 Ventu…

在Ubuntu 系统下开发GUI,用哪种开发工具比较好?

在Ubuntu系统下开发GUI&#xff0c;你可以考虑使用以下几种开发工具&#xff1a;Qt Creator&#xff1a;Qt Creator是一个跨平台的集成开发环境&#xff0c;专门用于开发基于Qt框架的应用程序。它提供了丰富的图形界面设计工具和代码编辑器&#xff0c;支持C和QML编程。Qt Crea…

REST API的基础:HTTP

在本文中&#xff0c;我们将深入探讨万维网数据通信的基础 - HTTP。 什么是超文本&#xff1f; HTTP&#xff08;超文本传输协议&#xff09;的命名源于“超文本”。 那么&#xff0c;什么是超文本&#xff1f; 想象一下由超链接组成的文本、图像和视频的混合物。这些链接充当我…

【2023.7.29】本文用于自己写文章时查看Markdown编辑器语法

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

texshop mac中文版-TeXShop for Mac(Latex编辑预览工具)

texshop for mac是一款可以在苹果电脑MAC OS平台上使用的非常不错的Mac应用软件&#xff0c;texshop for mac是一个非常有用的工具&#xff0c;广泛使用在数学&#xff0c;计算机科学&#xff0c;物理学&#xff0c;经济学等领域的合作&#xff0c;这些程序的标准tetex分布特产…

产品经理如何平衡用户体验与商业价值?

近期负责前端产品设计工作的小李忍不住抱怨&#xff1a;公司总是要求客户第一&#xff0c;实现客户良好体验&#xff0c;但在实际操作过程中&#xff0c;面向用户 体验提升的需求&#xff0c;研发资源计划几乎很难排上&#xff0c;资源都放在公司根据业务价值排序的需求…

Verilog语法学习——LV5_位拆分与运算

LV5_位拆分与运算 题目来源于牛客网 [牛客网在线编程_Verilog篇_Verilog快速入门 (nowcoder.com)](https://www.nowcoder.com/exam/oj?page1&tabVerilog篇&topicId301) 题目 题目描述&#xff1a; 现在输入了一个压缩的16位数据&#xff0c;其实际上包含了四个数据…

uniapp 语音文本播报功能

最近uniapp项目上遇到一个需求 就是在接口调用成功的时候加上语音播报 &#xff0c; ‘创建成功’ ‘开始成功’ ‘结束成功’ 之类的。 因为是固定的文本 &#xff0c;所以我先利用工具生成了 文本语音mp3文件&#xff0c;放入项目中&#xff0c;直接用就好了。 这里用到的工…

VoxPoser:使用大语言模型(GPT-4)来对机器人操作的可组合三维值图【论文解读】

这是最近斯坦福的李飞飞团队的一篇论文:VoxPoser: Composable 3D Value Maps for Robotic Manipulation with Language Models 主要是通过大语言模型LLM和视觉语言模型VLM结合&#xff0c;来对机器人做各种日常操作&#xff0c;我们可以先来看下实际效果&#xff1a;大语言模型…

2023牛客多校第三场 B.Auspiciousness

传送门 前题提要:没得说,赛时根本没想到dp,赛后翻各大题解看了很久,终于懂了dp的做法,故准备写一篇题解. 首先,先定义一下我们的 d p dp dp方程,考虑将处于 [ 1 , n ] [1,n] [1,n]的数当做小数,将处于 [ n 1 , 2 ∗ n ] [n1,2*n] [n1,2∗n]的数当做大数.那么对于我们的摸牌结…