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



使用VS创建一个C# WinForm解决方案,将窗体重命名为FrmHeartbeat的,替换窗体中的代码,可直接运行。或在文章后下载解决方案。
用于记录心形轮廓上每个点的坐标,以及该点相对中心的角度(用于绘制旋转心形)
///
/// 心形坐标
///
public class HeartPt
{
///
/// 点的角度(用于旋转)
///
public float Angle { get; set; }
///
/// 点的坐标
///
public PointF Pt { get; set; }
}
使用了两种心形函数,生成心形坐标点集
///
/// 生成心形1
///
///
///
///
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;
}
///
/// 生成心形2
///
///
///
///
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;
}
}
}
///
/// 中间已绘制的心形
///
List<PointF[]> FinishedHeartList = new List<PointF[]>();
///
/// 当前心形轮廓
///
List<HeartPt> HeartPtList = new List<HeartPt>();
///
/// 当前心形轮廓
///
List<PointF> CurrHeartPts = new List<PointF>();
Timer timer;
///
/// 当前心形坐标集序号
///
int CurrDrawPtIndex = 0;
///
/// 角度边长(越大越快)
///
float angleStep = 2f;
///
/// 生成心形1
///
///
///
///
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;
}
///
/// 生成心形2
///
///
///
///
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 };
///
/// 根据轮廓层次获取其对应颜色
///
///
///
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();
}
}
}
///
/// 心形坐标
///
public class HeartPt
{
///
/// 点的角度(用于旋转)
///
public float Angle { get; set; }
///
/// 点的坐标
///
public PointF Pt { get; set; }
}
个人认为,要实现本文的绘制有两点需要注意: