系列文章:
(二)计算机图形学基本图形的生成(直线DDA算法,直线中点算法,Bresenham画圆算法)--附源码
(三)计算机图形学基本图形的生成(二维图形裁剪Cohen-Sutherland算法+图形平移算法+图形旋转算法)--附源码
环境:Win10+Visual Studio 2022 Community
在本次实验中需要用到上一篇文章实验内容的代码及环境,详情请见:传送门
目录
一、实验目的
1、熟练掌握生成直线的DDA算法
2、熟练掌握生成直线的中点算法
3、熟练掌握生成圆的Bresenham算法
二、实验过程
1.生成直线的DDA算法
(1)利用已经建立好的DDA直线操作框架,用DDA算法生成直线函数替换系统提供的画线函数g.DrawLine
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
Graphics g = CreateGraphics(); //创建图形设备
Pen MyPen = new Pen(Color.Red, 1);
if (MenuID == 1)
{
if (PressNum == 0) //第一点,保留
{
FirstX = e.X;
FirstY = e.Y;
OldX = e.X;
OldY = e.Y;
}
else //第二点,画线
{
DDALine1(FirstX, FirstY, e.X, e.Y);
}
PressNum++;
if (PressNum >= 2)
{
PressNum = 0; //画线完毕,清零,为画下一条线做准备
}
}
}
(2)建立DDALine1函数实现DDA直线算法
private void DDALine1(int x0, int y0, int x1, int y1)
{
int x, flag;
float m, y;
Graphics g = CreateGraphics(); //创建图形设备
if (x0 == x1 && y0 == y1) return; //端点重叠,不画。这种情况不可忽略,否则程序有缺陷
if (x0 == x1) //垂直线
{
if (y0 > y1) //保证y0为最小
{
x = y0;
y0 = y1;
y1 = x;
}
for (x = y0; x <= y1; x++)
{
g.DrawRectangle(Pens.Red, x1, x, 1, 1); //画点函数,在(x1,x)处画红点
}
return;
}
if (y0 == y1) //水平线
{
if (x0 > x1)
{
x = x0; x0 = x1; x1 = x;
}
for (x = x0; x <= x1; x++)
{
g.DrawRectangle(Pens.Red, x, y0, 1, 1);
}
return;
}
if (x0 > x1) //按照算法,(x0,y0)是左端点。如果不满足,就将(x0,y0)、(x1,y1)互换
{
x = x0; x0 = x1; x1 = x;
x = y0; y0 = y1; y1 = x;
}
flag = 0; //记录线段种类
if (x1 - x0 > y1 - y0 && y1 - y0 > 0) flag = 1; //第一种线段,不做转化工作
if (x1 - x0 > y0 - y1 && y0 - y1 > 0) //第二种线段,转化
{
flag = 2;
y0 = -y0;
y1 = -y1; //关于X轴对称,图形点Y坐标加负号
}
if (y1 - y0 > x1 - x0) //第三种线段,关于y=x对称,图形点坐标x,y互换位置
{
flag = 3;
x = x0; x0 = y0; y0 = x;
x = x1; x1 = y1; y1 = x;
}
if (y0 - y1 > x1 - x0) //第四种线段转化为第一种线段
{
flag = 4;
x = x0; x0 = -y0; y0 = x;
x = x1; x1 = -y1; y1 = x;
}
m = (float)(y1 - y0) / (float)(x1 - x0);
for (x = x0, y = (float)y0; x <= x1; x++, y += m)
{
if (flag == 1) g.DrawRectangle(Pens.Red, x, (int)(y + 0.5), 1, 1);
if (flag == 2) g.DrawRectangle(Pens.Red, x, -(int)(y + 0.5), 1, 1);
if (flag == 3) g.DrawRectangle(Pens.Red, (int)(y + 0.5), x, 1, 1);
if (flag == 3) g.DrawRectangle(Pens.Red, (int)(y + 0.5), -x, 1, 1);
}
}
(3)运行结果
2.生成直线的中点算法
(1)在Form1. cs[设计]页面中,点击、展开菜单项“基本图形生成”,选中子菜单项“中点直线”,并将子菜单项“中点直线”的Name属性值改为"MidLine"
(2)双击“中点直线”菜单项,系统自动建立菜单响应函数MidLine_Click,在该函数中插入如下语句
private void MidLine_Click(object sender, EventArgs e)
{
MenuID = 2; PressNum = 0;
Graphics g = CreateGraphics(); //创建图形设备
g.Clear(BackColor1); //设置背景色
}
(3)因为中点直线的操作方法与DDA直线操作方法完全一样,因此只要能够对两者加以区分,就可以借用DDA直线鼠标操作实现程序部分。为此,做如下修改
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
Graphics g = CreateGraphics(); //创建图形设备
Pen MyPen = new Pen(Color.Red, 1);
if (MenuID == 1 || MenuID == 2)
{
if (PressNum == 0) //第一点,保留
{
FirstX = e.X;
FirstY = e.Y;
OldX = e.X;
OldY = e.Y;
}
else //第二点,画线
{
if (MenuID == 1)
DDALine1(FirstX, FirstY, e.X, e.Y);
if (MenuID == 2)
MidLine1(FirstX, FirstY, e.X, e.Y);
}
PressNum++;
if (PressNum >= 2)
{
PressNum = 0; //画线完毕,清零,为画下一条线做准备
}
}
}
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
Graphics g = CreateGraphics(); //创建图形设备
Pen BackPen = new Pen(BackColor1, 1);
Pen MyPen = new Pen(ForeColor1, 1);
if ((MenuID == 1 || MenuID == 2) && PressNum == 1)
{
if (!(e.X == OldX && e.Y == OldY))
{
g.DrawLine(BackPen, FirstX, FirstY, OldX, OldY);
g.DrawLine(MyPen, FirstX, FirstY, e.X, e.Y);
OldX = e.X;
OldY = e.Y;
}
}
}
(4)建立MidLine1函数实现中点直线算法
private void MidLine1(int x0, int y0, int x1, int y1)
{
int x, y, d, flag;
Graphics g = CreateGraphics(); //创建图形设备
if (x0 == x1 && y0 == y1) return; //端点重叠,不画
if (x0 == x1) //垂直线
{
if (y0 > y1)
{
x = y0;
y0 = y1;
y1 = x;
}
for (y = y0; y <= y1; y++)
{
g.DrawRectangle(Pens.Red, x1, y, 1, 1); //画点函数,在(x1,y)处画红点
}
return;
}
if (y0 == y1) //水平线
{
if (x0 > x1)
{
x = x0; x0 = x1; x1 = x;
}
for (x = x0; x <= x1; x++)
{
g.DrawRectangle(Pens.Red, x, y0, 1, 1);//画点函数,在(x,y0)处画红点
}
return;
}
if (x0 > x1) //起点(x0,y0)是左端点,如果不满足,就将(x0,y0)、(x1,y1)互换
{
x = x0; x0 = x1; x1 = x;
x = y0; y0 = y1; y1 = x;
}
flag = 0; //直线类别标记
if (x1 - x0 > y1 - y0 && y1 - y0 > 0) flag = 1; //第一种线段,不做转化工作
if (x1 - x0 > y0 - y1 && y0 - y1 > 0) //第二种线段转化为第一种线段
{
flag = 2;
y0 = -y0;
y1 = -y1;
}
if (y1 - y0 > x1 - x0) //第三种线段转化为第一种线段
{
flag = 3;
x = x0; x0 = y0; y0 = x;
x = x1; x1 = y1; y1 = x;
}
if (y0 - y1 > x1 - x0) //第四种线段转化为第一种线段
{
flag = 4;
x = x0; x0 = -y0; y0 = x;
x = x1; x1 = -y1; y1 = x;
}
x = x0; y = y0; d = (x1 - x0) - 2 * (y1 - y0);
while (x < x1 + 1)
{
if (flag == 1) g.DrawRectangle(Pens.Red, x, y, 1, 1);
if (flag == 2) g.DrawRectangle(Pens.Red, x, -y, 1, 1);
if (flag == 3) g.DrawRectangle(Pens.Red, y, x, 1, 1);
if (flag == 3) g.DrawRectangle(Pens.Red, y, -x, 1, 1);
x++;
if (d > 0)
{
d = d - 2 * (y1 - y0);
}
else
{
y++;
d = d - 2 * ((y1 - y0) - (x1 - x0));
}
}
}
(5)运行结果
3.生成圆的Bresenham算法
(1)在“基本图形生成”菜单项下选中子菜单“Bresenham圆”,将子菜单的Name属性值改为“BresenhamCircle”
(2)双击子菜单项“Bresenham圆”,在系统生成的菜单响应函数BresenhamCircle_Click中增添如下内容
private void BresenhamCircle_Click(object sender, EventArgs e)
{
MenuID = 5; PressNum = 0;
Graphics g = CreateGraphics(); //创建图形设备
g.Clear(BackColor1); //设置背景色
}
(3)和直线生成一样,这里的响应函数只是做一个基本图形生成的类型标识,真正的图形生成操作在鼠标操作中完成。鼠标画圆的操作方式确定为:先用鼠标确定圆心,再用鼠标确定圆上任意一点。在鼠标点击响应函数中插人如下语句
private void Form1_MouseClick(object sender, MouseEventArgs e)
{
Graphics g = CreateGraphics(); //创建图形设备
Pen MyPen = new Pen(Color.Red, 1);
if (MenuID == 1 || MenuID == 2)
{
if (PressNum == 0) //第一点,保留
{
FirstX = e.X;
FirstY = e.Y;
OldX = e.X;
OldY = e.Y;
}
else //第二点,画线
{
if (MenuID == 1)
DDALine1(FirstX, FirstY, e.X, e.Y);
if (MenuID == 2)
MidLine1(FirstX, FirstY, e.X, e.Y);
}
PressNum++;
if (PressNum >= 2)
{
PressNum = 0; //画线完毕,清零,为画下一条线做准备
}
}
if (MenuID == 5) //画圆部分
{
if (PressNum == 0) //圆心,保留
{
FirstX = e.X;
FirstY = e.Y;
}
else //圆上任意一点,确定半径
{
if (FirstX == e.X && FirstY == e.Y) return;
BresenhamCircle1(FirstX, FirstY, e.X, e.Y);
}
PressNum++;
if (PressNum >= 2)
{
PressNum = 0; //画圆完毕,清零,为画下一个圆做准备
}
}
}
(4)在鼠标移动响应函数中插入如下语句
private void Form1_MouseMove(object sender, MouseEventArgs e)
{
Graphics g = CreateGraphics(); //创建图形设备
Pen BackPen = new Pen(BackColor1, 1);
Pen MyPen = new Pen(ForeColor1, 1);
if ((MenuID == 1 || MenuID == 2) && PressNum == 1)
{
if (!(e.X == OldX && e.Y == OldY))
{
g.DrawLine(BackPen, FirstX, FirstY, OldX, OldY);
g.DrawLine(MyPen, FirstX, FirstY, e.X, e.Y);
OldX = e.X;
OldY = e.Y;
}
}
if (MenuID == 5 && PressNum == 1)
{
if (!(e.X == OldX && e.Y == OldY))
{
//求圆半径
double r = Math.Sqrt((FirstX - OldX) * (FirstX - OldX) + (FirstY - OldY) * (FirstY - OldY));
int r1 = (int)(r + 0.5); //取整
g.DrawEllipse(BackPen, FirstX - r1, FirstY - r1, 2 * r1, 2 * r1);//擦除旧圆
//求圆半径
r = Math.Sqrt((FirstX - e.X) * (FirstX - e.X) + (FirstY - e.Y) * (FirstY - e.Y));
r1 = (int)(r + 0.5); //取整
g.DrawEllipse(MyPen, FirstX - r1, FirstY - r1, 2 * r1, 2 * r1);
}
}
}
(5)g.DrawEllipse函数是系统提供的画椭圆函数,这里借助它,通过画一个长短轴相等的椭圆,实现画圆的橡皮筋。BresenhamCircle1函数是真正用圆的算法实现画圆的函数,还没建立。在程序Form1.cs程序中建立BresenhamCircle1函数如下
private void BresenhamCircle1(int x0, int y0, int x1, int y1)
{
int r, d, x, y;
Graphics g = CreateGraphics(); //创建图形设备
r = (int)(Math.Sqrt((x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0)) + 0.5);
x = 0; y = r; d = 3 - 2 * r;
while (x < y || x == y)
{
g.DrawRectangle(Pens.Blue, x + x0, y + y0, 1, 1);
g.DrawRectangle(Pens.Red, -x + x0, y + y0, 1, 1);
g.DrawRectangle(Pens.Green, x + x0, -y + y0, 1, 1);
g.DrawRectangle(Pens.Yellow, -x + x0, -y + y0, 1, 1);
g.DrawRectangle(Pens.Black, y + x0, x + y0, 1, 1);
g.DrawRectangle(Pens.Red, -y + x0, x + y0, 1, 1);
g.DrawRectangle(Pens.Red, y + x0, -x + y0, 1, 1);
g.DrawRectangle(Pens.Red, -y + x0, -x + y0, 1, 1);
x++;
if (d < 0 || d == 0)
{
d = d + 4 * x + 6;
}
else
{
y = y - 1;
d = d + 4 * (x - y) + 10;
}
}
}