GDI下用双缓冲实现橡皮筋技术C#
2017-11-18 本文已影响156人
放翁lcf
橡皮筋效果在图形系统中是很常见也很实用的功能。
在唐荣锡的《计算机图形学教程》中介绍为:“所谓橡皮筋技术就是在起点确定后,光标移出去定终点时,在屏幕上始终显示一条连结起点和光标中心的的直线,这条直线随着光标中心位置的变动而变动。
可以知道,一个实现过程如下:
- 按下鼠标左键:记录该点坐标。
- 移动鼠标:画出上一个端点到鼠标所在点的直线,并删除或者覆盖掉上一条直线。
- 再次点击左键时,又选择一个点,相当于回到第一步
用双缓冲技术实现的效果是比较好的,而用自动撤销线即:
ControlPaint.DrawReversibleLine(Point start,Point end, Color BackColor)
闪烁比较严重。
橡皮筋技术画多边形.jpg
下面给出C#中用双缓冲实现橡皮筋的代码,代码中注释还是相对详细的。该代码在VS2017下运行良好。
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Threading.Tasks;
using System.Windows.Forms;
/*用双缓冲技术实现GDI+下的橡皮筋效果
*控件:rubberInGDIplus--主窗体;intimePoiLbl--实时坐标展示
* 左键选点,中键删除最后一个选择的点;右键完成多边形选择;Del键可删除所有点;
* */
namespace rubberInGDIplus {
public partial class rubberEffectForm : Form {
Pen rubPen = new Pen(Color.SpringGreen, 2);//橡皮筋效果用笔;rubber pen
Point readPoi; //intime point
bool useRubber = true;
Graphics gp,gh;
private Bitmap bitmap = null;//虽然可以不用怎么多的Bitmap和 Graphics
public List<Point> poilst = new List<Point>(); //多边形端点
public rubberEffectForm() {
InitializeComponent();
this.Paint += new PaintEventHandler(this.rubberEffectForm_Paint); //初始化载入的像素方格
this.MouseClick += new MouseEventHandler(this.rubberEffectForm_Click); //监听点击事件
this.MouseMove += new MouseEventHandler(this.rubberEffectForm_MouseMove); //监听鼠标移动事件
this.KeyUp += new KeyEventHandler(this.rubberEffectForm_KeyUp);//键盘按键事件
//激活双缓冲技术
SetStyle(ControlStyles.UserPaint, true);
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.DoubleBuffer, true);
}
private void rubberEffectForm_Load(object sender, EventArgs e) {
}
private void rubberEffectForm_Paint(object sender, PaintEventArgs e) {
gh = e.Graphics;
bitmap = new Bitmap(ClientSize.Width, ClientSize.Height);
gp = Graphics.FromImage(bitmap);
gp.Clear(this.BackColor);
gp.SmoothingMode = SmoothingMode.AntiAlias;//设置抗锯齿平滑模式
if (useRubber && readPoi != null) {//橡皮筋在使用中
int plct = poilst.Count;
if (plct == 0) {//还没有点
} else if (plct == 1) {//只存了一个点
gp.DrawLine(rubPen, poilst[0], readPoi);
} else {//两点及以上
for (int i = 0; i < plct; i++) {
if (i == plct - 1) {
gp.DrawLine(rubPen, poilst[0], readPoi);
gp.DrawLine(rubPen, poilst[i], readPoi);
} else {
gp.DrawLine(rubPen, poilst[i], poilst[i + 1]);
}
}
}
} else if (useRubber == false && readPoi != null) {//按下中键后
int plct = poilst.Count;
if (plct == 0 | plct == 1) {
} else {//两点及以上
for (int i = 0; i < plct; i++) {
if (i == plct - 1) {
gp.DrawLine(rubPen, poilst[0], poilst[i]);
} else {
gp.DrawLine(rubPen, poilst[i], poilst[i + 1]);
}
}
}
}
gh.DrawImage(bitmap, 0, 0);//display
}
private void rubberEffectForm_Click(object sender, MouseEventArgs e) {
if (e.Button == MouseButtons.Left) {//鼠标左击
useRubber = true;
Point readPoint = this.PointToClient(Control.MousePosition);//基于工作区的坐标
readPoi = readPoint;
intimePoiLbl.Text = readPoint.ToString();
//drawVertex(gp, readPoint); //画端点(顶点) 由于橡皮筋的覆盖,端点看不出来
poilst.Add(readPoint);//加点到list<point>里
} else if (e.Button == MouseButtons.Right) { //右键
useRubber = false;
this.Refresh();
//drawRim();//画边框
} else if (e.Button == MouseButtons.Middle) { //中键
int plast = poilst.Count - 1;
poilst.RemoveAt(plast);
useRubber = true;
this.Refresh();
}
}
private void rubberEffectForm_MouseMove(object sender, MouseEventArgs e) {
readPoi = this.PointToClient(Control.MousePosition);//基于工作区的坐标
Graphics gw = this.CreateGraphics();
if (useRubber) { //在橡皮筋模式内
gw.Clear(BackColor);
int plct = poilst.Count;
if (plct == 0) {// ==0: pass
} else if (plct == 1) {
gw.DrawLine(rubPen, poilst[0], readPoi);
} else {//两点及以上
for (int i = 0; i < plct; i++) {
if (i == plct - 1) {//画到最后一点了
gw.DrawLine(rubPen, poilst[i], readPoi);
gw.DrawLine(rubPen, poilst[0], readPoi);
} else {
gw.DrawLine(rubPen, poilst[i], poilst[i + 1]);
}
}
}
} else {
}
intimePoiLbl.Text = readPoi.ToString();
}
private void rubberEffectForm_KeyUp(object sender, KeyEventArgs e) {
if (e.KeyCode == Keys.Delete) {
poilst.Clear(); //画的点也要清除
this.Refresh();
} else if (e.KeyCode == Keys.Back | e.KeyCode == Keys.Escape) {
int plast = poilst.Count - 1;
poilst.RemoveAt(plast);
useRubber = true;
this.Refresh();
}
}
#region 可用可不用的函数
//画端点(顶点)
private void drawVertex(Graphics g, Point poi) {
Size sz = new Size(4, 4);
g.FillEllipse(Brushes.Red, new Rectangle(poi, sz));
}
private void drawRim() {//画边框
if (poilst.Count == 0)
return;
Point[] poi = new Point[poilst.Count];
for (int i = 0; i < poilst.Count; i++) {
poi[i] = poilst[i];
}
gp.DrawPolygon(new Pen(Color.Blue, 2), poi);
}
#endregion
}
}
还可以设置鼠标指针的形状,在窗体属性的Cursor中,由Default变为Cross,这样就像ArcGIS或者CorelDRAW的效果了。
完整工程以及更新文件可以参见我的GitHub-rubberInGDIplus
直接点击或者复制链接:
https://github.com/QLWeilcf/pixelCGframewk/tree/master/rubberInGDIplus
我觉得它具有的功能是:能够很好地作为图形学以及矢量多边形小软件的框架
具体的应用项目可以看我的扫描线填充多边形的代码。
- scanLineToFillPolygon:GitHub