이 분야에 대한 정보를 찾는 사람들이 많을 것입니다. 아래에서 제가 한 내용을 살펴보시면 도움이 될 것입니다.
러버밴드 기술을 구현하기 위해 두 가지 방법을 사용했습니다.
첫 번째는 ControlPaint.DrawReversibleLine(Point start, Point end, Color BackColor) 메서드를 사용하는 것입니다. 원리: 시작점을 지정합니다. 화면에 시작점과 끝점 내에 지정된 배경색으로 되돌릴 수 있는 선을 그립니다. 동일한 선을 다시 그리면 이 메서드의 결과가 반전됩니다. 이 방법을 사용하여 선을 그리는 것은 화면의 영역을 반전시키는 것과 유사하지만 더 넓은 범위의 색상에 대해 더 나은 성능을 제공합니다.
여기서 주의할 점은 시작점과 끝점은 화면을 기준으로 하므로 PointTOScreen(Point p) 메서드를 사용하여 변환합니다.
아쉽게도 마우스를 드래그하면 그려진 변환(즉, 선분, 제 연구 분야에서는 선분을 변환이라고 부릅니다)에는 선분이 없습니다. 변환을 그리려면 왼쪽 버튼을 눌렀을 때 다시 그려야만 달성할 수 있습니다(선을 표시할 필요가 없으면 MouseDown() 메서드에서 Invalidate()를 주석 처리하면 됩니다). DrawReversibleLine() 메서드를 사용할 때 배경색 backColor=(a,r,g,b)를 사용하므로 자동으로 색상을 반전할 수 있으며, 왼쪽 버튼을 눌렀을 때 다시 그리기 위해서는 배경색 반전 reversebackColor=(가 필요합니다. a', r', g', b'), 그러면 배경색의 반대색을 얻는 방법은 무엇입니까? 내가 사용한 것은 255에서 원래 배경색 r, g, b를 빼고 투명도는 변경되지 않은 상태에서, 즉 a'=a;r'=255-r;g'=255-g;b'=255입니다. -b; 그런 다음 이 색상으로 정의된 브러시로 다시 칠합니다.
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Drawing.Drawing2D;//包含这个高级二维图形命名空间 namespace ReverseLines { public partial class Form1 : Form { public Form1() { InitializeComponent(); //激活双缓冲技术 SetStyle(ControlStyles.UserPaint, true); SetStyle(ControlStyles.AllPaintingInWmPaint, true); SetStyle(ControlStyles.DoubleBuffer, true); } private Point[][] tranGroup = new Point[1000][];//变换组 private int tranNumb = 0;//变换序号 private int pushNumb = 0;//左键按下情况:0为开始画变换,1为结束 private Point curP;//存储变换时鼠标的当前点 private Point startP, oldP;//变换的起点和鼠标移动时的当前点 private Graphics g0,g3;//窗口绘图面和采用双缓冲时的临时绘图面 private Point endPoint;//存储右键按下时放弃绘制相连变换的鼠标点 private Color clr,clr1;//获取窗体背景色和反转背景色 private Pen p;//重画变换时所用的笔 private Bitmap bitmap = null;//双缓冲时用的位图 private void Form1_Paint(object sender, PaintEventArgs e) { g0 = e.Graphics; bitmap = new Bitmap(ClientSize.Width, ClientSize.Height);//创建临时位图 g3 = Graphics.FromImage(bitmap);//从位图创建绘图面 g3.Clear(this.BackColor);//清除背景色 g3.SmoothingMode = SmoothingMode.AntiAlias;//设置抗锯齿平滑模式 //在临时位图上重画变换,抗锯齿,带线冒 for (int i = 0; i < tranNumb; i++) { g3.DrawLine(p, tranGroup[i][0], tranGroup[i][1]); } //把临时位图拷贝到窗体绘图面 g0.DrawImage(bitmap, 0, 0); } private void Form1_Load(object sender, EventArgs e) { clr = this.BackColor;//获取窗体背景色 clr1 = Color.FromArgb(clr.A, 255 - clr.R, 255 - clr.G, 255 - clr.B);//反转背景色 p = new Pen(clr1, 1);//定义鼠标左键按下并移动时绘制变换所用的笔 //自定义线冒 AdjustableArrowCap cap = new AdjustableArrowCap(3, 3); cap.WidthScale = 3; cap.BaseCap = LineCap.Square; cap.Height = 3; p.CustomEndCap = cap; //循环绘制变换组中的变换 for (int i = 0; i < 1000; i++) { tranGroup[i] = new Point[2]; } } private void Form1_MouseDown(object sender, MouseEventArgs e) { Graphics g2 = CreateGraphics(); //判断变换数 if (tranNumb >= 999) { pushNumb = 0; Capture = false; return; } //左键按下 if (e.Button == MouseButtons.Left) { if (pushNumb == 0)//判断是否是折线的开始 { if (endPoint.X != e.X || endPoint.Y != e.Y) { pushNumb++; startP.X = e.X; startP.Y = e.Y; oldP.X = e.X; oldP.Y = e.Y; Capture = true;//捕获鼠标 } } else if (pushNumb == 1)//如果不是一段新的折线的开始 { ControlPaint.DrawReversibleLine(PointToScreen(startP), PointToScreen(new Point(e.X, e.Y)), clr); ControlPaint.DrawReversibleLine(PointToScreen(startP), PointToScreen(new Point(e.X, e.Y)), clr); //把变换存入变换组中 curP.X = e.X; curP.Y = e.Y; tranGroup[tranNumb][0] = startP; tranGroup[tranNumb][1] = curP; tranNumb++; startP.X = e.X; startP.Y = e.Y; //存储一段折线的最后一个点的坐标 endPoint.X = e.X; endPoint.Y = e.Y; } } //右键按下 if (e.Button == MouseButtons.Right) { //变换数超过变换组最大限度 if (pushNumb == 0) return; //变换数没有超过变换组最大限度 pushNumb = 0;//一段折线结束 Capture = false;//释放鼠标 //绘制最后一个变换 ControlPaint.DrawReversibleLine(PointToScreen(startP), PointToScreen(new Point(e.X, e.Y)), clr); } //失效重画,为抗锯齿 Invalidate(); g2.Dispose(); } private void Form1_MouseMove(object sender, MouseEventArgs e) { Graphics g1 = CreateGraphics(); //左键按下并移动鼠标 if (pushNumb == 1) { if (oldP.X != e.X || oldP.Y != e.Y) { //在屏幕上指定的起始点和结束点内绘制具有指定背景色的可逆线 //再次绘制同一条线会逆转该方法的结果。使用该方法绘制线类似于反转屏幕的一个区域, //不过它提供了更好的性能适用于更广泛的颜色。 ControlPaint.DrawReversibleLine(PointToScreen(startP), PointToScreen(oldP), clr); ControlPaint.DrawReversibleLine(PointToScreen(startP), PointToScreen(new Point(e.X, e.Y)), clr); //存储一个变换的终点,作为下一变换的起点 oldP.X = e.X; oldP.Y = e.Y; } } g1.Dispose(); } //释放资源 private void Form1_FormClosed(object sender, FormClosedEventArgs e) { g3.Dispose(); bitmap.Dispose(); g0.Dispose(); } } }
두 번째 방법은 마우스 드래그 시 지워야 할 변형을 배경색을 직접 이용하여 그리는 것이고, 현재 브러시를 이용하여 일정한 변형을 그리는 방법이다. 이런 식으로 마우스를 드래그할 때 그려지는 변환선이 나타납니다.
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Text; using System.Windows.Forms; using System.Drawing.Drawing2D; namespace Shiqu2 { public partial class Form1 : Form { public Form1() { InitializeComponent(); //激活双缓冲技术 SetStyle(ControlStyles.UserPaint, true); SetStyle(ControlStyles.AllPaintingInWmPaint, true); SetStyle(ControlStyles.DoubleBuffer, true); } private Point[][] tranGroup = new Point[1000][];//变换组 private int tranNumb = 0;//变换序号 private int pushNumb = 0;//左键按下情况:0为开始画变换,1为结束 private Point curP;//存储变换时鼠标的当前点 private Point startP, oldP;//变换的起点和鼠标移动时的当前点 private Graphics g0, g3;//窗口绘图面和采用双缓冲时的临时绘图面 public Pen curPen;//一个变换确定并要绘制时所用的画笔 private Point endPoint;//存储右键按下时放弃绘制相连变换的鼠标点 private Color clr;//获取窗体背景色 private Pen p;//重画变换时所用的笔 private Bitmap bitmap = null;//双缓冲时用的位图 private void Form1_Paint(object sender, PaintEventArgs e) { g0 = e.Graphics; bitmap = new Bitmap(ClientSize.Width, ClientSize.Height);//创建临时位图 g3 = Graphics.FromImage(bitmap);//从位图创建绘图面 g3.Clear(this.BackColor);//清除背景色 g3.SmoothingMode = SmoothingMode.AntiAlias;//设置抗锯齿平滑模式 //在临时位图上重画已有的变换,抗锯齿,带线冒 for (int i = 0; i < tranNumb; i++) { g3.DrawLine(curPen, tranGroup[i][0], tranGroup[i][1]); } //把临时位图拷贝到窗体绘图面 g0.DrawImage(bitmap, 0, 0); } private void Form1_Load(object sender, EventArgs e) { curPen = new Pen(Color.Black, 1);//定义一个变换确定并要绘制时所用的画笔 clr = this.BackColor;//获取窗体背景色 p = new Pen(clr, 1);//定义鼠标移动是重画所以的画笔 //自定义线冒 AdjustableArrowCap cap = new AdjustableArrowCap(3, 3); cap.WidthScale = 3; cap.BaseCap = LineCap.Square; cap.Height = 3; curPen.CustomEndCap = cap; p.CustomEndCap = cap; //初始化绘制变换组中的变换 for (int i = 0; i < 1000; i++) { tranGroup[i] = new Point [2]; } } private void Form1_MouseDown(object sender, MouseEventArgs e) { Graphics g2=CreateGraphics (); //判断变换数 if (tranNumb >= 999) { pushNumb = 0; Capture = false; return; } //左键按下 if (e.Button == MouseButtons.Left) { if (pushNumb == 0)//判断是否是折线的开始 { if (endPoint.X != e.X || endPoint.Y != e.Y) { pushNumb++; startP.X = e.X; startP.Y = e.Y; oldP.X = e.X; oldP.Y = e.Y; Capture = true;//捕获鼠标 } } else if (pushNumb == 1)//如果不是一段新的折线的开始 { g2.DrawLine(curPen, startP, new Point(e.X, e.Y)); //把变换存入变换组中 curP.X = e.X; curP.Y = e.Y; tranGroup[tranNumb][0] = startP; tranGroup[tranNumb][1] = curP; tranNumb++; startP.X = e.X; startP.Y = e.Y; //存储一段折线的最后一个点的坐标 endPoint.X = e.X; endPoint.Y = e.Y; } } //右键按下 if (e.Button == MouseButtons.Right) { //变换数超过变换组最大限度 if (pushNumb == 0) return; //变换数没有超过变换组最大限度 pushNumb = 0;//一段折线结束 Capture = false;//释放鼠标 } //失效重画,为抗锯齿 Invalidate(); g2.Dispose(); } private void Form1_MouseMove(object sender, MouseEventArgs e) { Graphics g1 = CreateGraphics(); //左键按下并移动鼠标 if (pushNumb ==1) { if (oldP .X !=e.X||oldP .Y !=e.Y) { g1.DrawLine(p, startP, oldP);//用背景色绘制原来的变换 g1.DrawLine(curPen, startP, new Point(e.X, e.Y));//用当前画笔绘制当前变换 //用当前绘制已有的变换,防止它们被擦除 for (int i = 0; i < tranNumb; i++) { g1.DrawLine(curPen, tranGroup[i][0], tranGroup[i][1]); } //存储一个变换的终点,作为下一变换的起点 oldP.X = e.X; oldP.Y = e.Y; } } } private void Form1_FormClosed(object sender, FormClosedEventArgs e) { //释放资源 g3.Dispose(); bitmap.Dispose(); g0.Dispose(); } } }
위의 두 방법 모두 더블 버퍼링 기술을 사용합니다. 먼저 클라이언트 영역과 동일한 크기의 비트맵 비트맵을 만든 다음 비트맵을 사용하여 임시 그리기 표면 g3을 만든 다음 g3에 그립니다. 변환한 다음 그리기 표면 g 양식을 사용하여 그림을 그린 후 비트맵을 그립니다.
앤티앨리어싱 기술: 단 한 문장 g3.SmoothingMode = SmoothingMode.AntiAlias 단, 왼쪽 버튼을 누른 상태에서 마우스를 드래그하는 경우에는 앤티앨리어싱 기술을 사용할 수 없으므로 주의하시기 바랍니다.