直线、圆环绘制算法

一、直线算法

1、数值微分法(DDA算法)

DDA算法主要是利用了增量的思想,通过同时对x和y各增加一个小增量,计算下一步的x和y值。

在这里插入图片描述

根据上式可知△x=1时,x每递增1,y就递增k,所以只需要对x和y不断递增就可以得到下一点的函数值,这样避免了对每一个像素都使用直线方程来计算,消除了浮点数乘法运算。

#include <Windows.h>
#include <iostream>
#include <cmath>
using namespace std;

const int ScreenWidth = 500;
const int ScreenHeight = 500;

LRESULT CALLBACK WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message) {
    case WM_CLOSE:
        DestroyWindow(hWnd);
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
        break;
    }
    return 0;
}

void DDALine(int x0,int y0,int x1,int y1,HDC hdc)
{
    int i=1;
    float dx, dy, length, x, y;
    if (fabs(x1 - x0) >= fabs(y1 - y0))
        length = fabs(x1 - x0);
    else
        length = fabs(y1 - y0);
    dx = (x1 - x0) / length;
    dy = (y1 - y0) / length;
    x = x0;
    y = y0;
    while (i<=length)
    {
        SetPixel(hdc,int(x + 0.5), ScreenHeight-40-int(y + 0.5), RGB(0, 0, 0));
        x = x + dx;
        y = y + dy;
        i++;
    }
}
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int nShowCmd)
{
    WNDCLASS wcs;
    wcs.cbClsExtra = 0;                                         // 窗口类附加参数  
    wcs.cbWndExtra = 0;                                         // 窗口附加参数  
    wcs.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);    // 窗口DC背景  
    wcs.hCursor = LoadCursor(hInstance, IDC_CROSS);             // 鼠标样式  
    wcs.hIcon = LoadIcon(NULL, IDI_WINLOGO);                    // 窗口icon  
    wcs.hInstance = hInstance;                                  // 应用程序实例  
    wcs.lpfnWndProc = (WNDPROC)WinProc;
    wcs.lpszClassName = "CG";
    wcs.lpszMenuName = NULL;
    wcs.style = CS_VREDRAW | CS_HREDRAW;
    RegisterClass(&wcs);
    HWND hWnd;
    hWnd = CreateWindow("CG","DrawLine", WS_OVERLAPPEDWINDOW, 200, 200, ScreenWidth, ScreenHeight, NULL, NULL, hInstance, NULL);
    ShowWindow(hWnd, nShowCmd);
    UpdateWindow(hWnd);
    MSG msg;

    // hdc init
    HDC hdc = GetDC(hWnd);

    // 绘图,画一条从点(0,0)到(100,350)的直线
    DDALine(0, 0, 100, 350, hdc);// 消息循环  
    while (GetMessage(&msg, 0, NULL, NULL)) {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    // release
    ReleaseDC(hWnd, hdc);
    return 0;
}

以上是DDA算法的实现,WinMain和WinProc函数是Windows API编程特有的,我们只需要关注DDALine这个绘图函数,该函数传入两个点的坐标画出一条直线。

首先判断起点和终点间x轴和y轴哪个轴向的跨度更大(斜率范围),为了防止丢失像素,应让跨度更大的轴向每次自增1,这样能获得更精确的结果。

接下来就没什么好说的,依次让x和y加上增量然后四舍五入就行了,浮点数四舍五入可以直接用int(x+0.5)计算,setPixel函数用于设置像素的颜色值(需要传入窗口的hdc句柄),由于Windows窗口坐标的原点在左上角,所以拿窗口高度减去y值就可以转换成平常习惯的左下角坐标系了。


2、Bresenham算法

DDA算法尽管消除了浮点数乘法运算,但仍存在浮点数加法和取整操作,效率仍有待提高,1965年Bresenham提出了更好的直线生成算法,成为了时至今日图形学领域使用最广泛的直线生成算法,该算法采用增量计算,借助一个误差量的符号确定下一个像素点的位置,该算法中不存在浮点数,只有整数运算,大大提高了运行效率。

我们先只考虑斜率在0-1之间的情况,从线段左端点开始处理,并逐步处理每个后续列,每确定当前列的像素坐标(xi,yi)后,那么下一步需要在列xi+1上确定y的值,此时y值要么不变,要么增加1,这是因为斜率在0-1之间,x增长比y快,所以x每增加1,y的增量是小于1的。

对于左端点默认为其像素坐标,下一列要么是右方的像素,要么是右上方的像素,设右上方像素到直线的距离为d2,右方像素到直线的距离为d1,显然只需要判断直线离哪个像素点更近也就是d1-d2的符号即可找到最佳像素。

在这里插入图片描述

所以可以推出以下式子:

在这里插入图片描述

其中△x起点到终点x轴上距离,△y为y轴上距离,k=△y/△x,c是常量,与像素位置无关。

令ei=△x(d1-d2),则ei的计算仅包括整数运算,符号与d1-d2一致,称为误差量参数,当它小于0时,直线更接近右方像素,大于0时直线更接近右上方像素。

可利用递增整数运算得到后继误差量参数,计算如下:

在这里插入图片描述

所以选择右上方像素时(yi+1-yi=1):

在这里插入图片描述

选择右方像素时(yi+1-yi=0):

在这里插入图片描述

初始时,将k=△y/△x代入△x(d1-d2)中可得到起始像素的第一个参数:

在这里插入图片描述

斜率在0-1之间的Bresenham算法代码实现(替换上面程序中DDALine即可):

void Bresenham_Line(int x0, int y0, int x1, int y1, HDC hdc)
{
    int dx, dy, e, x=x0, y=y0;
    dx = x1 - x0; dy = y1 - y0;
    e = 2 * dy - dx;
    while (x<=x1)
    {
        SetPixel(hdc, x, ScreenHeight-40-y, RGB(0, 0, 0));
        if (e >= 0)//选右上方像素
        {
            e = e + 2 * dy - 2 * dx;
            y++;
        }
        else//选右方像素
        {
            e = e + 2 * dy;
        }
        x++;
    }
}

要实现任意方向的Bresenham算法也很容易,斜率在0-1之间意味着直线位于坐标系八象限中的第一象限,如果要绘制第二象限的直线,只需要利用这两个象限关于直线x=y对称的性质即可,可以先将x和y值互换先在第一象限进行计算,然后调用SetPixel时再将x和y值反过来,在第二象限中绘制,其他象限也是类似的思路。

绘制任意方向直线的Bresenham算法代码实现:

void Bresenham_Line(int x0, int y0, int x1, int y1, HDC hdc)
{
    int flag = 0;
    int dx = abs(x1 - x0);
    int dy = abs(y1 - y0);
    if (dx == 0 && dy == 0)   return;
    if (abs(x1 - x0) < abs(y1 - y0))
    {
        flag = 1;
        swap(x0, y0);
        swap(x1, y1);
        swap(dx, dy);
    }
    int tx = (x1 - x0) > 0 ? 1 : -1;
    int ty = (y1 - y0) > 0 ? 1 : -1;
    int x = x0;    
    int y = y0;
    int dS = 2 * dy;   int dT = 2 * (dy - dx);
    int e = dS - dx;
    SetPixel(hdc, x0, y0, RGB(0,0,0));
    while (x != x1)
    {
        if (e < 0)
            e += dS;
        else
        {
            y += ty;  e += dT;
        }
        x += tx;
        if (flag)
            SetPixel(hdc, y, ScreenHeight - 40 - x, RGB(0, 0, 0));
        else
            SetPixel(hdc, x, ScreenHeight - 40 - y, RGB(0, 0, 0));
    }
}

二、圆形算法

1、中点画圆法

首先是中点画圆法,考虑圆心在原点,半径为R的圆在第一象限内的八分之一圆弧,从点(0, R)到点(R/ , R/ )顺时针方向确定这段圆弧。假定某点Pi(xi, yi)已经是该圆弧上最接近实际圆弧的点,那么Pi的下一个点只可能是正右方的P1或右下方的P2两者之一,如图所示:

在这里插入图片描述

构造判别函数:F(x, y)= x2 + y2 – R2

当F(x, y)= 0,表示点在圆上,当F(x, y)> 0,表示点在圆外,当F(x, y)< 0,表示点在圆内。如果M是P1和P2的中点,则M的坐标是(xi + 1, yi – 0.5),当F(xi + 1, yi – 0.5)< 0时,M点在圆内,说明P1点离实际圆弧更近,应该取P1作为圆的下一个点。同理分析,当F(xi + 1, yi – 0.5)> 0时,P2离实际圆弧更近,应取P2作为下一个点。当F(xi + 1, yi – 0.5)= 0时,P1和P2都可以作为圆的下一个点,算法约定取P2作为下一个点。

现在将M点坐标(xi + 1, yi – 0.5)带入判别函数F(x, y),得到判别式d:

d = F(xi + 1, yi – 0.5)= (xi + 1)2 + (yi – 0.5)2 – R2

若d < 0,则取P1为下一个点,此时P1的下一个点的判别式为:

d’ = F(xi + 2, yi – 0.5)= (xi + 2)2 + (yi – 0.5)2 – R2

展开后将d带入可得到判别式的递推关系:

d’ = d + 2xi + 3

若d > 0,则取P2为下一个点,此时P2的下一个点的判别式为:

d’ = F(xi + 2, yi – 1.5)= (xi + 2)2 + (yi – 1.5)2 – R2

展开后将d带入可得到判别式的递推关系:

d’ = d + 2(xi - yi) + 5

特别的,在第一个象限的第一个点(0, R)时,可以推倒出判别式d的初始值d0:

d0 = F(1, R – 0.5) = 1 – (R – 0.5)2 – R2 = 1.25 - R

根据上面的分析,可以写出中点画圆法的算法。考虑到圆心不在原点的情况,需要对计算出来的坐标进行了平移,下面就是通用的中点画圆法的源代码:

#include <stdio.h>
#include <graphics.h>
#include <conio.h>
#define x0 400
#define y0 300                    //定义全局变量x0,y0:坐标轴中心(x0,y0)
void Middle_point_draw_circle(int x1, int y1, int r) 
{
	int d0, x = 0, y = r;	//d0是判别式的值
	d0 = 1.25 - r;   		//判别式的初始值,1.25可以改为1
	while (x < y) 
	{
		if (d0 >= 0) 
		{
			d0 = d0 + 2 * (x - y) + 5;            //d0一定要先比x,y更新
			x += 1;                				 //因为d0表达式中的x,y是上一个点
			y -= 1;
			putpixel(((x + x1) + x0), (y0 - (y + y1)), RED);         //(x,y)
			putpixel(((-x + x1) + x0), (y0 - (y + y1)), RED);        //(-x,y)
			putpixel(((y + x1) + x0), (y0 - (x + y1)), RED);         //(y,x)
			putpixel(((-y + x1) + x0), (y0 - (x + y1)), RED);        //(-y,x)
			putpixel(((x + x1) + x0), (y0 - (-y + y1)), RED);        //(x,-y)
			putpixel(((-x + x1) + x0), (y0 - (-y + y1)), RED);       //(-x,-y)
			putpixel(((y + x1) + x0), (y0 - (-x + y1)), RED);        //(y,-y)
			putpixel(((-y + x1) + x0), (y0 - (-x + y1)), RED);       //(-y,-x)
			Sleep(50);
		}
		else 
		{
			d0 = d0 + 2 * x + 3;
			x += 1;
			y = y;
			putpixel(((x + x1) + x0), (y0 - (y + y1)), RED);         //(x,y)
			putpixel(((-x + x1) + x0), (y0 - (y + y1)), RED);        //(-x,y)
			putpixel(((y + x1) + x0), (y0 - (x + y1)), RED);         //(y,x)
			putpixel(((-y + x1) + x0), (y0 - (x + y1)), RED);        //(-y,x)
			putpixel(((x + x1) + x0), (y0 - (-y + y1)), RED);        //(x,-y)
			putpixel(((-x + x1) + x0), (y0 - (-y + y1)), RED);       //(-x,-y)
			putpixel(((y + x1) + x0), (y0 - (-x + y1)), RED);        //(y,-y)
			putpixel(((-y + x1) + x0), (y0 - (-x + y1)), RED);       //(-y,-x)
			Sleep(50);
		}
	}
}
void main() 
{
	int x1, y1, r;
	printf("请输入中点画圆算法圆心坐标(x1,y1)和圆的半径r:\n");
	scanf("%d %d %d", &x1, &y1, &r);
	initgraph(x0 * 2, y0 * 2);		    	//初始化图形窗口大小
	setbkcolor(WHITE);
	cleardevice();
	setcolor(BLACK);
	line(x0, 0, x0, y0 * 2);				//坐标轴X
	line(0, y0, x0 * 2, y0);			 	//坐标轴Y
	Middle_point_draw_circle(x1, y1, r);             //中点画圆算法
	_getch();                                        //等待一个任意输入结束
	closegraph();                                    //关闭图形窗口
}

2、Bresenham算法

中点画圆法中,计算判别式d使用了浮点运算,影响了圆的生成效率。如果能将判别式规约到整数运算,则可以简化计算,提高效率。于是人们针对中点画圆法进行了多种改进,其中一种方式是将d的初始值由1.25 – R改成1 – R,考虑到圆的半径R总是大于2,因此这个修改不会响d的初始值的符号,同时可以避免浮点运算。还有一种方法是将d的计算放大两倍,同时将初始值改成3 – 2R,这样避免了浮点运算,乘二运算也可以用移位快速代替,采用3 – 2R为初始值的改进算法,称为Bresenham算法。

源代码展示:

#include <stdio.h>
#include <graphics.h>
#include <conio.h>
#define x0 400
#define y0 300                    //定义全局变量x0,y0:坐标轴中心(x0,y0)
void Middle_point_draw_circle(int x1, int y1, int r) 
{
	int d0, x = 0, y = r;		//d0是判别式的值
	d0 = 3 - 2*r;   			//判别式的初始值
	while (x < y) 
	{
		if (d0 >= 0) 
		{
			d0 = d0 + 4 * (x - y) + 10;           	 //d0一定要先比x,y更新
			x += 1;                					//因为d0表达式中的x,y是上一个点
			y -= 1;
			putpixel(((x + x1) + x0), (y0 - (y + y1)), RED);         //(x,y)
			putpixel(((-x + x1) + x0), (y0 - (y + y1)), RED);        //(-x,y)
			putpixel(((y + x1) + x0), (y0 - (x + y1)), RED);         //(y,x)
			putpixel(((-y + x1) + x0), (y0 - (x + y1)), RED);        //(-y,x)
			putpixel(((x + x1) + x0), (y0 - (-y + y1)), RED);        //(x,-y)
			putpixel(((-x + x1) + x0), (y0 - (-y + y1)), RED);       //(-x,-y)
			putpixel(((y + x1) + x0), (y0 - (-x + y1)), RED);        //(y,-y)
			putpixel(((-y + x1) + x0), (y0 - (-x + y1)), RED);       //(-y,-x)
			Sleep(50);
		}
		else 
		{
			d0 = d0 + 4 * x + 6;
			x += 1;
			y = y;
			putpixel(((x + x1) + x0), (y0 - (y + y1)), RED);         //(x,y)
			putpixel(((-x + x1) + x0), (y0 - (y + y1)), RED);        //(-x,y)
			putpixel(((y + x1) + x0), (y0 - (x + y1)), RED);         //(y,x)
			putpixel(((-y + x1) + x0), (y0 - (x + y1)), RED);        //(-y,x)
			putpixel(((x + x1) + x0), (y0 - (-y + y1)), RED);        //(x,-y)
			putpixel(((-x + x1) + x0), (y0 - (-y + y1)), RED);       //(-x,-y)
			putpixel(((y + x1) + x0), (y0 - (-x + y1)), RED);        //(y,-y)
			putpixel(((-y + x1) + x0), (y0 - (-x + y1)), RED);       //(-y,-x)
			Sleep(50);
		}
	}
}
void main() 
{
	int x1, y1, r;
	printf("请输入中点画圆算法圆心坐标(x1,y1)和圆的半径r:\n");
	scanf("%d %d %d", &x1, &y1, &r);
	initgraph(x0 * 2, y0 * 2);		    	//初始化图形窗口大小
	setbkcolor(WHITE);
	cleardevice();
	setcolor(BLACK);
	line(x0, 0, x0, y0 * 2);				//坐标轴X
	line(0, y0, x0 * 2, y0);			 	//坐标轴Y
	Middle_point_draw_circle(x1, y1, r);             //中点画圆算法
	_getch();                                        //等待一个任意输入结束
	closegraph();                                    //关闭图形窗口
}

文章内容转自
https://www.cnblogs.com/LiveForGame/p/11706904.html
https://blog.csdn.net/l59565455/article/details/87473897

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
计算机图形学课程设计任务书[1] ———————————————————————————————— 作者: ———————————————————————————————— 日期: 南京工程学院 课程设计任务书                课  程 名  称  计算机图形学  院(系、部、中心) 计算机工程学院 专       业  计算机科学与技术 班       级 多媒体091  起  止   日 期 2011.12.19—12.25 指   导 教 师 丁宇辰                     " 1.课程设计应达到的目的 " "《计算机图形学》是一门理论与实践并重的课程,既要注重理解基本知识,也要注重培" "养基本技能。相应的计算机图形学课程设计对帮助学生全面牢固地掌握课堂教学内" "容、培养学生编程能力、提高学生全面素质具有很重要的意义。通过本课程的实践" "环节,使学生具有使用计算机图形学的基本算法开发简单应用程序的能力,并理解 " "软件系统开发的基本流程。 " "2.课程设计题目及要求 " "选择实现以下任务中的1~2个。 " "1、《多边形填充》(直线、圆、椭圆利用算法自编函数进行调用) " "具体要求: " "(1)能利用交互手段实现多边形、圆或椭圆的绘制,并能实现填充; " "(2)自己编写划线函数; " "(3)自己编写填充函数; " "2、《二维图形裁剪》 " "具体要求: " "设计程序实现二维图形的裁剪; " "设计坐标系,画出二维图形; " "用中点法或区域代码的方法实现裁剪。 " "3、《图形变换》 " "具体要求: " "绘制出图形; " "实现图形的平移、旋转、缩放、错切等变换。 " "4、《绘制曲线》 " "具体要求: " "编写一个交互绘制Bezier或B样条曲线的程序; " "给出控制点就能绘制曲线; " "自己编写函数实现。 " "5、《可见面判断》 " "具体要求: " "绘制多面体图形; " "输出各面的可见性; " "做任意角度旋转变换,再判断各面的可见性 " "6、《颜色渐变三角形》 " "具体要求: " "绘制三角形; " "实现颜色的各种变换; " "实现填充色的渐变(从一个方向开始颜色由浅入深) " "7、《绘制三维五角星》 " "具体要求: " "编写一个绘制三维五角星的程序; " "实现填充,中心点最红,渐变到外围颜色变浅; " "8. 《观察和投影变换》 " "具体要求: " "设计一立体图形; " "输出其三视图; " "定义观察窗口,输出其三维线架图。 " "9.《设计一个动画程序》 " "具体要求: " "设计一个图形,完成该图形的动画演示; " "给出使用说明。 " "10.《用OpenGL绘制一三维图形》 " "具体要求: " "绘制三维图形; " "添加纹理。 " "11.《设计一个分形树》 " "具体要求: " "写出算法分析步骤; " "实现该算法; " "改变输入,观察分形图形。 " "12.《设计B样条曲面》 " "具体要求: " "对B样条曲面的数学表达式进行分析,写出算法步骤; " "实现该算法; " "改变输入,观察曲面图形。 " "13.《自造字库,设计一个特大型汉字》 " "具体要求: " "构造汉字字库; " "实现汉字的输出; " "对汉字进行填充及各种变换。 " "14.在屏幕上使用鼠标左键绘制数量大于4的任意顶点形成控制多边形,单机鼠标 " "右键绘制三次B样条曲线,同时在控制多边形的每一个特征三角形内用虚线显示三 " "次B样条曲线的几何生成原理。 " "具体要求: " "分析原理,写出算法; " "实现该算法; " "15.给定"树叶"轮廓的控制点,要求使用B样条曲线的特殊构造技巧,使用B样条绘制" "所构造的树叶。 " "具体要求: " "分析原理,写出算法; " "实现该算法; " "16.样条曲线和正弦曲线同时绘制,进行效果对比,绘制的长度不少于两个周期。" "具体要求: " "分析原理,写出算法; " "实现该算法; " "17.自定义一个三维图形,可参考下图,绘出其三视图。 " "具体要求: " "分析原理,写出算法; " "实现该算法; " "18.模仿Windows画图的界面,编写一个交互画图的小系统程序。 " "具体要求: " "写出实现方法; " "实现该方法; " "19.设计一个多边形,利用活化边表算法进行填充。 " "具体要求: " "写出实现方法; " "实现该方法; " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " " "3.课程设计任务及工作

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值