心动(GDI+)

文章目录

  • 前言
  • 实现步骤
  • 源代码
    • 心形坐标类
    • 心形函数
    • 定时器方法
    • 绘制函数
    • 完整源码
  • 结束语

前言

近期学习了一段时间的GDI+,突然想着用GDI+绘制点啥,用来验证下类与方法。有兴趣的,可以查阅Windows GDI+学习笔记相关文章。
效果展示
心动GDI+

实现步骤

  1. 定义心形函数 。
    这里实现两种心形函数
    函数一:
    心形函数一
    函数二:
    心形函数二
  2. 生成指定宽度、高度的心形坐标集 。
  3. 在定时器中控制,定时刷新绘制区域,控制当前绘制的心形点集,轮廓的点序号、心跳状态。
  4. 根据当前心形点集绘制一个心形用于绕着另一个心形边缘点旋转并逐渐绘制出另一个完整心形。
  5. 用渐变色填充最内层心形。
  6. 增加心形宽、高,生成另一个心形。
  7. 用大的心形围绕待绘制的心形边缘旋转逐渐绘制出另一个完整心形。
  8. 当有两层心形时,交替填充显示最内一层或二层的心形,使其有跳动的效果。
  9. 继续加大心形的宽、高,并在外面绘制更多层的心形。
  10. 当达到一定层数后,用另一个心形函数再次重复上次的绘制过程。

源代码

使用VS创建一个C# WinForm解决方案,将窗体重命名为FrmHeartbeat的,替换窗体中的代码,可直接运行。或在文章后下载解决方案。

心形坐标类

用于记录心形轮廓上每个点的坐标,以及该点相对中心的角度(用于绘制旋转心形)

    /// <summary>/// 心形坐标/// </summary>public class HeartPt{/// <summary>/// 点的角度(用于旋转)/// </summary>public float Angle { get; set; }/// <summary>/// 点的坐标/// </summary>public PointF Pt { get; set; }}

心形函数

使用了两种心形函数,生成心形坐标点集

        /// <summary>/// 生成心形1/// </summary>/// <param name="width"></param>/// <param name="height"></param>/// <returns></returns>private List<HeartPt> GetHeartPts(float width, float height){var result = new List<HeartPt>();// 生成心形坐标for (float angle = 0; angle < 360; angle += angleStep){var radian = Math.PI * angle / 180;var sinT = Math.Sin(radian);double x = 16 * Math.Pow(sinT, 3);double y = 13 * Math.Cos(radian) - 5 * Math.Cos(2 * radian) - 2 * Math.Cos(3 * radian) - Math.Cos(4 * radian);// 转换坐标到画布上var xCoord = (float)(x * (width / 2));var yCoord = (float)(-y * (height / 2));result.Add(new HeartPt(){Angle = angle,Pt = new PointF(xCoord, yCoord),});}return result;}/// <summary>/// 生成心形2/// </summary>/// <param name="width"></param>/// <param name="height"></param>/// <returns></returns>private List<HeartPt> GetHeartPts2(float width,float height){width = width * 10;height = height * 10;var result = new List<HeartPt>();var sqrt2 = Math.Sqrt(2);// 生成心形坐标for (float angle = 0; angle < 360; angle+= angleStep){var radian = Math.PI * angle / 180;var sinT = Math.Sin(radian);double x = -sqrt2 * Math.Pow(sinT, 3);var cosT = Math.Cos(radian);double y = 2 * cosT - Math.Pow(cosT, 2) - Math.Pow(cosT, 3);// 转换坐标到画布上var xCoord = (float)(x * (width / 2));var yCoord = (float)(-y * (height / 2));result.Add(new HeartPt(){Angle = angle,Pt = new PointF(xCoord, yCoord),});}return result;}

定时器方法

1、定时刷新绘制区域
2、控制当前心形轮廓点的序号
3、当绘制完一个心形后,记录并生成另一个扩大的心形
4、控制心跳频率

        private void Timer_Tick(object sender, EventArgs e){this.Invalidate();CurrDrawPtIndex++;CurrDrawPtIndex = HeartPtList.Count == 0 ? 0 : CurrDrawPtIndex % HeartPtList.Count;if (CurrDrawPtIndex == 0){if (heartWidth > MaxHeartWidth){//换样式,重绘heartWidth = MinHeartWidth;beatCount = 0;beat = false;FinishedHeartList.Clear();heartType++;heartType = heartType % 2;}else if (CurrHeartPts.Count > 0){FinishedHeartList.Add(CurrHeartPts.Select(z => new PointF(z.X, z.Y)).ToArray());}heartWidth++;if (heartType == 0){HeartPtList = GetHeartPts(heartWidth, heartWidth);}else{HeartPtList = GetHeartPts2(heartWidth, heartWidth);}CurrHeartPts = new List<PointF>();}CurrHeartPts.Add(HeartPtList[CurrDrawPtIndex].Pt);beatCount++;if (beatCount >= 600 / timer.Interval){//多久跳动一次beatCount = 0;if (FinishedHeartList.Count > 1){beat = !beat;}}}

绘制函数

1、设计绘制参数、清空背景
2、控制心形中心点坐标
3、绘制已记录的心形,控制最内两层心形的填充和其它层心形的颜色
4、通过待绘制心形点集的序号控制心形绘制位置与旋转角度
5、注意矩阵的变化,使旋转心形的控制点与待绘制轮廓点一致。

        private void FrmHeart_Paint(object sender, PaintEventArgs e){e.Graphics.CompositingQuality = CompositingQuality.HighQuality;e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;e.Graphics.Clear(Color.White);if (HeartPtList.Count == 0) return;//心形中心点e.Graphics.TranslateTransform(this.Width / 2, this.Height / 2.2f);var pen = FinishedHeartList.Count % 2 == 0 ? Pens.Red : Pens.Pink;Color color = Color.Red;for (int i = FinishedHeartList.Count - 1; i >= 0; i--){var heart = FinishedHeartList[i];if (i <= 1){//渐变色填充                    PathGradientBrush pathGBrush = new PathGradientBrush(heart);{if (beat){//跳动时,最里面不绘制if (i == 0) break;color = Color.Red;}else{color = Colors[i];}pathGBrush.SurroundColors = new Color[] { color };pathGBrush.CenterPoint = new PointF(0, heart.Max(z=>z.Y)/10f);pathGBrush.CenterColor = Color.White;var gPath = new GraphicsPath();gPath.AddClosedCurve(heart);e.Graphics.FillPath(pathGBrush, gPath);}}else{color = GetColor(i);using (var penC = new Pen(color,2)){//心形轮廓e.Graphics.DrawClosedCurve(penC, heart);}}}color = GetColor(FinishedHeartList.Count);using (var penC = new Pen(color,2)){if (CurrHeartPts.Count > 2){//绘制当前心形                   e.Graphics.DrawCurve(penC, CurrHeartPts.ToArray());}var currHeartPt = HeartPtList[CurrDrawPtIndex].Pt;var firstHeartPt = HeartPtList[0].Pt;//绘制旋转的心形using (var gPath = new GraphicsPath()){gPath.AddClosedCurve(HeartPtList.Select(z => z.Pt).ToArray());using (var matrix = new Matrix()){matrix.Translate(currHeartPt.X, currHeartPt.Y, MatrixOrder.Append);matrix.Translate(-firstHeartPt.X, -firstHeartPt.Y, MatrixOrder.Append);matrix.RotateAt(HeartPtList[CurrDrawPtIndex].Angle, currHeartPt, MatrixOrder.Append);gPath.Transform(matrix);e.Graphics.DrawPath(penC, gPath);}}}}

完整源码

    public partial class FrmHeartbeat : Form{public FrmHeartbeat(){InitializeComponent();this.FormBorderStyle = FormBorderStyle.FixedToolWindow;this.Width = 960;this.Height = 800;this.StartPosition = FormStartPosition.CenterScreen;this.DoubleBuffered = true;this.Load += new System.EventHandler(this.FrmHeart_Load);this.Paint += new System.Windows.Forms.PaintEventHandler(this.FrmHeart_Paint);}private void FrmHeart_Load(object sender, EventArgs e){this.Text = "心动";timer = new Timer();timer.Interval = 20;//控制绘制速度timer.Tick += Timer_Tick;timer.Start();}const int MinHeartWidth = 15;const int MaxHeartWidth = 25;private int heartWidth = MinHeartWidth;private bool beat = false;private int beatCount = 0;private int heartType = 0;private void Timer_Tick(object sender, EventArgs e){this.Invalidate();CurrDrawPtIndex++;CurrDrawPtIndex = HeartPtList.Count == 0 ? 0 : CurrDrawPtIndex % HeartPtList.Count;if (CurrDrawPtIndex == 0){if (heartWidth > MaxHeartWidth){//换样式,重绘heartWidth = MinHeartWidth;beatCount = 0;beat = false;FinishedHeartList.Clear();heartType++;heartType = heartType % 2;}else if (CurrHeartPts.Count > 0){FinishedHeartList.Add(CurrHeartPts.Select(z => new PointF(z.X, z.Y)).ToArray());}heartWidth++;if (heartType == 0){HeartPtList = GetHeartPts(heartWidth, heartWidth);}else{HeartPtList = GetHeartPts2(heartWidth, heartWidth);}CurrHeartPts = new List<PointF>();}CurrHeartPts.Add(HeartPtList[CurrDrawPtIndex].Pt);beatCount++;if (beatCount >= 600 / timer.Interval){//多久跳动一次beatCount = 0;if (FinishedHeartList.Count > 1){beat = !beat;}}}/// <summary>/// 中间已绘制的心形/// </summary>List<PointF[]> FinishedHeartList = new List<PointF[]>();/// <summary>/// 当前心形轮廓/// </summary>List<HeartPt> HeartPtList = new List<HeartPt>();/// <summary>/// 当前心形轮廓/// </summary>List<PointF> CurrHeartPts = new List<PointF>();Timer timer;/// <summary>/// 当前心形坐标集序号/// </summary>int CurrDrawPtIndex = 0;/// <summary>/// 角度边长(越大越快)/// </summary>float angleStep = 2f;/// <summary>/// 生成心形1/// </summary>/// <param name="width"></param>/// <param name="height"></param>/// <returns></returns>private List<HeartPt> GetHeartPts(float width, float height){var result = new List<HeartPt>();// 生成心形坐标for (float angle = 0; angle < 360; angle += angleStep){var radian = Math.PI * angle / 180;var sinT = Math.Sin(radian);double x = 16 * Math.Pow(sinT, 3);double y = 13 * Math.Cos(radian) - 5 * Math.Cos(2 * radian) - 2 * Math.Cos(3 * radian) - Math.Cos(4 * radian);// 转换坐标到画布上var xCoord = (float)(x * (width / 2));var yCoord = (float)(-y * (height / 2));result.Add(new HeartPt(){Angle = angle,Pt = new PointF(xCoord, yCoord),});}return result;}/// <summary>/// 生成心形2/// </summary>/// <param name="width"></param>/// <param name="height"></param>/// <returns></returns>private List<HeartPt> GetHeartPts2(float width,float height){width = width * 10;height = height * 10;var result = new List<HeartPt>();var sqrt2 = Math.Sqrt(2);// 生成心形坐标for (float angle = 0; angle < 360; angle+= angleStep){var radian = Math.PI * angle / 180;var sinT = Math.Sin(radian);double x = -sqrt2 * Math.Pow(sinT, 3);var cosT = Math.Cos(radian);double y = 2 * cosT - Math.Pow(cosT, 2) - Math.Pow(cosT, 3);// 转换坐标到画布上var xCoord = (float)(x * (width / 2));var yCoord = (float)(-y * (height / 2));result.Add(new HeartPt(){Angle = angle,Pt = new PointF(xCoord, yCoord),});}return result;}Color[] Colors = new Color[] { Color.Red, Color.Pink };/// <summary>/// 根据轮廓层次获取其对应颜色/// </summary>/// <param name="index"></param>/// <returns></returns>private Color GetColor(int index){Color color;if (beat){color = Colors[(index + 1) % 2];}else{color = Colors[index % 2];}return color;}private void FrmHeart_Paint(object sender, PaintEventArgs e){e.Graphics.CompositingQuality = CompositingQuality.HighQuality;e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;e.Graphics.Clear(Color.White);if (HeartPtList.Count == 0) return;//心形中心点e.Graphics.TranslateTransform(this.Width / 2, this.Height / 2.2f);var pen = FinishedHeartList.Count % 2 == 0 ? Pens.Red : Pens.Pink;Color color = Color.Red;for (int i = FinishedHeartList.Count - 1; i >= 0; i--){var heart = FinishedHeartList[i];if (i <= 1){//渐变色填充                    PathGradientBrush pathGBrush = new PathGradientBrush(heart);{if (beat){//跳动时,最里面不绘制if (i == 0) break;color = Color.Red;}else{color = Colors[i];}pathGBrush.SurroundColors = new Color[] { color };pathGBrush.CenterPoint = new PointF(0, heart.Max(z=>z.Y)/10f);pathGBrush.CenterColor = Color.White;var gPath = new GraphicsPath();gPath.AddClosedCurve(heart);e.Graphics.FillPath(pathGBrush, gPath);}}else{color = GetColor(i);using (var penC = new Pen(color,2)){//心形轮廓e.Graphics.DrawClosedCurve(penC, heart);}}}color = GetColor(FinishedHeartList.Count);using (var penC = new Pen(color,2)){if (CurrHeartPts.Count > 2){//绘制当前心形                   e.Graphics.DrawCurve(penC, CurrHeartPts.ToArray());}var currHeartPt = HeartPtList[CurrDrawPtIndex].Pt;var firstHeartPt = HeartPtList[0].Pt;//绘制旋转的心形using (var gPath = new GraphicsPath()){gPath.AddClosedCurve(HeartPtList.Select(z => z.Pt).ToArray());using (var matrix = new Matrix()){matrix.Translate(currHeartPt.X, currHeartPt.Y, MatrixOrder.Append);matrix.Translate(-firstHeartPt.X, -firstHeartPt.Y, MatrixOrder.Append);matrix.RotateAt(HeartPtList[CurrDrawPtIndex].Angle, currHeartPt, MatrixOrder.Append);gPath.Transform(matrix);e.Graphics.DrawPath(penC, gPath);}}}}private void FrmHeartbeat_Click(object sender, EventArgs e){if(timer.Enabled){timer.Stop();}else{timer.Start();}}}/// <summary>/// 心形坐标/// </summary>public class HeartPt{/// <summary>/// 点的角度(用于旋转)/// </summary>public float Angle { get; set; }/// <summary>/// 点的坐标/// </summary>public PointF Pt { get; set; }}

结束语

个人认为,要实现本文的绘制有两点需要注意:

  1. 心形函数
    使用一个函数来实现一个完美的心形绘制,这决对不是一件简单的事,本文中的两个函数可能是数学家的呕心沥血之作,向前辈致敬!但对于我们使用它,可能就是一件相对简单的事了。
  2. 控制旋转心形点与待绘制心形轮廓点一致
    在图形的绘制过程中,矩阵的应用一直是个难点(至少对于本人来说),本文中要控制旋转心形与待绘制心形的点一致,使其感觉是用一个心形来绘制另一个心形,其中的矩阵变换,从结果回看,感觉很简单,但在实际应用过程,往往需要重复多次去尝试(肯定是数学理论没过关),有时仅仅因为顺序的不同,导致结果天差地别。在了解了GDI+大部分函数与功能后,对于矩阵变换还是需要花更多时间去研究。
    完整源码下载

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【微服务】部署mysql集群,主从复制,读写分离

两台服务器做如下操作 1.安装mysqldocker pull mysql:5.72.启动以及数据挂载 mkdir /root/mysql/data /root/mysql/log /root/mysql/conf touch my.conf //mysql的配置文件docker run --name mysql \ -e MYSQL_ROOT_PASSWORD123456 \ -v /root/mysql/data:/var/lib/mysql \ -v…

HarmonyOS鸿蒙学习笔记(28)@entry和@Component的生命周期

entry和Component的生命周期 entry和Component的关系Component生命周期Entry生命周期 生命周期流程图生命周期展示示例代码参考资料 HarmonyOS的生命周期可以分为Compnent的生命周期和Entry的生命周期&#xff0c;也就是自定义组件的生命周期和页面的生命周期。 entry和Compone…

RabbitMQ-发布/订阅模式

RabbitMQ-默认读、写方式介绍 RabbitMQ-直连交换机(direct)使用方法 目录 1、发布/订阅模式介绍 2、交换机(exchange) 3、fanout交换机的使用方式 3.1 声明交换机 3.2 发送消息到交换机 3.2 扇形交换机发送消息代码 3.2 声明队列&#xff0c;用于接收消息 3.3 binding …

sigmoid, softmax

∙ \bullet ∙ sigmoid函数 值域(0,1) 常用于二分类问题 ∙ \bullet ∙ softmax函数 每一项的区间范围的(0,1) 所有项相加的和为1. 常用于多分类问题 ∙ \bullet ∙ 区别&#xff1a; softmax 当类别数是2时&#xff0c;它退化为二项分布&#xff0c;而它和sigmoid真正的区别…

解决VSCode右键没有Open In Default Browser问题

在VSCode进行Web小程序测试时&#xff0c;我们在新建的HTML文件中输入 !会自动生成页面代码骨架&#xff0c;写入内容后&#xff0c;我们想要右键在浏览器中预览。发现右键没有“Open In Default Browser”选项。原因是没有安装插件。 下面是解决方案&#xff1a;首先在VSCode找…

探索 Android Studio 中的 Gemini:加速 Android 开发的新助力

探索 Android Studio 中的 Gemini&#xff1a;加速 Android 开发的新助力 在 Gemini 时代的下一篇章中&#xff0c;Gemini融入了更多产品中&#xff0c;Android Studio 正在使用 Gemini 1.0 Pro 模型&#xff0c;使 Android 开发变得更快、更简单。 Studio Bot 现已更名为 And…

The Isle恐龙岛服务器开服联机教程

服务端区别&#xff1a;The lsle 是测试服 &#xff1b;The lsle Evrima 是正式服&#xff08;运行内存需要上到12G才可以运行&#xff09; 1、购买后登录服务器 进入控制面板后会出现正在安装的界面&#xff0c;安装大约5分钟&#xff08;如长时间处于安装中请联系我们的客服人…

Unity 自定义编辑器根据枚举值显示变量

public class Test : MonoBehaviour {[HideInInspector][Header("数量")][SerializeField]public int num;[Header("分布类型")][SerializeField]public DistributionType distType;[HideInInspector][Header("位置")][SerializeField]public Li…

Vue之组件基础(插槽)

在HTML中&#xff0c;开发者可以在双标签内添加一些信息。而在Vue中&#xff0c;组件以标签的形式引用&#xff0c;那么如何在组件的标签内添加一些信息并将信息渲染到页面中呢?其实&#xff0c;Vue 提供了插槽&#xff0c;专门用来实现这样的效果。 一.什么是插槽 Vue为组件…

视频修复工具助你完成高质量的视频作品!

在短视频发展兴起的时代&#xff0c;各种视频层出不穷的出现在了视野中&#xff0c;人们已经从追求数量转向追求质量。内容相同的视频&#xff0c;你视频画质好、质量高的更受大家欢迎&#xff0c;那么如何制作高质量、高清晰度的视频呢&#xff1f;与您分享三个视频修复工具。…

命名空间,缺省参数和函数重载

前言&#xff1a;本文章主要介绍一些C中的小语法。 目录 命名空间 namespace的使用 访问全局变量 namespace可以嵌套 不同文件中定义的同名的命名空间可以合并进一个命名空间&#xff0c;并且其中不可以有同名的变量 C中的输入和输出 缺省参数&#xff08;默认参数&#…

电脑的kernelbase.dll故障怎么处理?kernelbase.dll是什么文件

遇到由于“kernelbase.dll”文件出错导致的应用程序崩溃或系统不稳定的问题。这种情况不仅会影响工作效率&#xff0c;还可能导致数据损失或更严重的系统问题。kernelbase.dll是Windows操作系统中的一个关键系统文件&#xff0c;它包含了多个执行基础系统功能的程序代码。因此&…

3389连接器,3389连接器如何进行安全设置

在计算机网络领域&#xff0c;3389端口作为Windows系统默认的远程桌面协议&#xff08;RDP&#xff09;端口&#xff0c;在远程办公、技术支持等场景中发挥着重要作用。然而&#xff0c;由于其广泛的使用和直接暴露在互联网上的特性&#xff0c;3389端口也极易成为黑客攻击的目…

python 贪心算法(Greedy Algo)

贪婪是一种算法范式&#xff0c;它逐步构建解决方案&#xff0c;始终选择提供最明显和直接收益的下一个部分。贪婪算法用于解决优化问题。 如果问题具有以下属性&#xff0c;则可以使用贪心法解决优化问题&#xff1a; 每一步&#xff0c;我们都可以做出当前看来最好的选择&…

git 恢复本地文件被误删除

查找自己执行命令出现的文件移除 或者创建的地方找到提交的 哈希值 然后执行 命令 git checkout c818f15&#xff08;这个后面是你执行的哈希代码&#xff09; main 里面有个代码值 把这个复制到你的命令行就好了 执行 然后就恢复文件了 还有一个是查找命令日志的 如果不小心…

[数据集][目标检测]水下管道泄漏破损检测数据集VOC+YOLO格式2069张2类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;2069 标注数量(xml文件个数)&#xff1a;2069 标注数量(txt文件个数)&#xff1a;2069 标注…

企业百度百科如何修改

百度百科是一个可以让我们快速的了解一个企业情况的地方&#xff0c;同时也让我们的企业展示了什么&#xff0c;还有哪些是可以做的。 注册与登录 首先&#xff0c;你需要注册一个百度账号&#xff0c;并通过邮箱或手机进行验证。登录后&#xff0c;可以开始创建或编辑百度百科…

你还不知道的APP安全测试项总结!

一、安装包测试 1.1、关于反编译 目的是为了保护公司的知识产权和安全方面的考虑等&#xff0c;一些程序开发人员会在源码中硬编码一些敏感信息&#xff0c;如密码。而且若程序内部一些设计欠佳的逻辑&#xff0c;也可能隐含漏洞&#xff0c;一旦源码泄漏&#xff0c;安全隐患…

深度学习入门-第3章-神经网络

前面的待补充 3.6 手写数字识别 3.6.1 MNIST 数据集 本书提供了便利的 Python 脚本 mnist.py &#xff0c;该脚本支持从下载 MNIST 数据集到将这些数据转换成 NumPy 数组等处理&#xff08;mnist.py 在 dataset 目录下&#xff09;。 使用 mnist.py 时&#xff0c;当前目录必须…

PolygonalSurfaceContourLineInterpolator 多边形交互器

1. 效果&#xff1a; 2.简介&#xff1a; 可以实现在多边形上进行交互&#xff0c;选择&#xff1b;在多边形曲面上实现轮廓点的交互绘制。 该类的使用需要结合 vtkPolygonalSurfacePointPlacer 类&#xff0c;定位点的功能也就是拾取器。 前提&#xff1a;输入的多边形曲面…