计算机图形学:直线的扫描转换算法解析与实现

news2025/2/25 13:39:13

直线的扫描转换:

DDA算法:

推理:

在计算机显示图形时,由于显示计算机的分辨率是有限的所以我们在绘制图形时需要将图形从连续量转换成离散量才能完成图形的绘制,直线的扫描转换就是将连续量转换为离散量的过程。
对于任意给我们两点 ( x 0 , y 0 ) (x_0, y_0) (x0,y0) ( x 1 , y 1 ) (x_1, y_1) (x1,y1)我们可以得到小面的一些量:
k = y 1 − y 0 x 1 − x 0 k = \frac{y_1-y_0}{x_1-x_0} k=x1x0y1y0
此值的意义表示的是在x轴上自增1,在y轴上的增量。如图所示:
在这里插入图片描述

这样我们得到斜率之后,我们就知道在x轴上增加1,y轴上的增量了,但是目前我们仍旧不知道该怎么将这个这两点所确定的直线转换为离散的点集。
我们将这个图形转化为下图看一下:
在这里插入图片描述
在这里插入图片描述

也就是说对于直线上一点我们可以使用最接近这一点的像素来表示这一点。
所以我们就可以就可以将直线转换为一个一个离散的点了,为了获取最多的能够表示这一条直线的点,对于斜率为k的直线,我们需要沿着增量最快的轴作为自变量来增加,因为像素是一个挨着一个,所以作为自变量的轴每一次增加1,因为k的取值可以变化,无论k值怎样变化,第一次决策的点都应该属于下面的八个点,这样我们么一次抉择下一个点就是在周围的八个点之间做一个决策:
在这里插入图片描述
在这里插入图片描述

当k选择一个具体的值之后如k=0.5则直线的下一次取点的决策范围应该为:
在这里插入图片描述

现在我们已经说明了为什么要选择增量最快的轴作为自变量轴,同时也说明了为什么作为自变量的轴每一次要增加1而不是增加2或者其他的数字。
如果不使用增量最快的轴作为自变量轴则会出现什么情况能。如起始点(2,2),斜率为k的直线选择变化慢的轴作为自变量,则决策范围为:
在这里插入图片描述

可以看出这种取法会导致我们选取的点不是最符合直线的点。
所以我们要采取自变量变化快的轴作为我们的自变量,每次变化1.
如果根据起点与终点确定了点的坐标,我们同样要根据点的坐标进行舍入,来确定整数坐标的像素作为表是直线的点,如何进行舍入呢,我们以(2,2)点表示的像素为例,我们只需要确定(x,y)是不是属于红色区域的点
在这里插入图片描述

公式为:
1.5 < = x < 2.5 → x = 2 1.5 < = y < 2.5 → y = 2 1.5<=x<2.5\rightarrow x = 2\\ 1.5<=y<2.5\rightarrow y = 2 1.5<=x<2.5x=21.5<=y<2.5y=2
因为c语言中的取整规则是向下取整,所以我们在确定
点的时候要将x,y分别加上0.5,用(x+0.5, y+0.5)取整来确定像素点。

公式推导

起点 ( x 起 , y 起 ) 终点 ( x 终 , y 终 ) k = y 终 − y 起 x 终 − x 起 Δ x = x 终 − x 起 Δ y = y 终 − y 起 k = Δ y Δ x 如果 0 ≤ k ≤ 1 则说明 x 轴变化快,选择 x 作为自变量 起始点 ( x 起 , y 起 ) : x 1 = x 起 + 1 y 1 = y 起 + k 选取点的时候选取: ( ⌊ x 1 + 0.5 ⌋ , ⌊ y 1 + 0.5 ⌋ ) x 2 = x 1 + 1 y 2 = y 1 + k 选取点的时候选取: ( ⌊ x 2 + 0.5 ⌋ , ⌊ y 2 + 0.5 ⌋ ) . . . . . . 起点(x_起, y_起)终点(x_终, y_终)\\ k = \frac{y_终-y_起}{x_终-x_起}\\ \Delta x = x_终- x_起\\ \Delta y = y_终-y_起\\ k = \frac{\Delta y}{\Delta x}\\ 如果0\leq k\leq1则说明x轴变化快,选择x作为自变量\\ 起始点(x_起, y_起):\\ x_1 = x_起 + 1\\ y_1 = y_起 + k\\ 选取点的时候选取:(\lfloor x_1+0.5\rfloor, \lfloor y_1+0.5\rfloor)\\ x_2 = x_1 + 1\\ y_2 = y_1 + k\\ 选取点的时候选取:(\lfloor x_2+0.5\rfloor, \lfloor y_2+0.5\rfloor)\\ ...... 起点(x,y)终点(x,y)k=xxyyΔx=xxΔy=yyk=ΔxΔy如果0k1则说明x轴变化快,选择x作为自变量起始点(x,y):x1=x+1y1=y+k选取点的时候选取:(⌊x1+0.5,y1+0.5⌋)x2=x1+1y2=y1+k选取点的时候选取:(⌊x2+0.5,y2+0.5⌋)......
所以我们先计算k,然后一步一步的进行计算即可。
但是上述只讨论了 0 < = k < = 1 0<=k<=1 0<=k<=1这一种情况,但是这已经够用了,因为对于坐标系内任意一点都可以通过一些对称轴转化至 0 < = k < = 1 0<=k<=1 0<=k<=1区域内。
对于 k > 1 k>1 k>1的情况,我们知道此时变化较快的轴为y轴所以选取y轴为增加1的轴,其余分析同上。
如图:
在这里插入图片描述

我们可以通过对称轴y=x将上方区域的点转换到下方区域。其他区域也是同理。将点进行对称时一定要按照以起点为原点进行对称,也就是说这涉及到一个坐标系变换的问题,我们根据坐标系的变换规则我们可以知道
(1)当要进行终点在起始点的左上方时:
在这里插入图片描述

$x = x1+2*(x0-x1) =2x0-x1\
y = y1
$
其余同理可得:
(2)当终点在起始点的左下方的时候:
x = x 1 + 2 ∗ ( x 0 − x 1 ) = 2 ∗ x 0 − x 1 y = y 1 + 2 ∗ ( y 0 − y 1 ) = 2 ∗ y 0 − x 1 x = x1+2*(x0-x1) =2*x0-x1\\ y = y1 + 2*(y0-y1) = 2*y0-x1 x=x1+2(x0x1)=2x0x1y=y1+2(y0y1)=2y0x1
(3)当终点在起始点的右下方的时候:
$
x =x1\
y = y1 + 2
(y0-y1) = 2*y0-x1
$
然后通过对称过去的点计算出点的坐标然后对称回来进行绘制即可。
我们可以写出下述算法:

void paintline(CPoint start, CPoint end, CDC* pDC, int deltax, int deltay, int flag ) {
	int num;
	float x1, y1, xIncreation, yIncreation;
	CPoint point;
	CPoint end2;
	switch (flag) {
	case 1:
		end2.x = end.x;
		end2.y = end.y;
		break;
	case 2:
		end2.x = 2 * start.x - end.x;
		end2.y = end.y;
		break;
	case 3:
		end2.x = 2 * start.x - end.x;
		end2.y = 2 * start.y - end.y;
		break;
	case 4:
		end2.y = 2 * start.y - end.y;
		end2.x = end.x;
		break;
	}
	deltax = end2.x - start.x;
	deltay = end2.y - start.y;
	num = deltax > deltay ? deltax : deltay;
	if (deltax > deltay) {
		yIncreation = 1.0 * deltay / deltax;
		xIncreation = 1;
	}
	else {
		xIncreation = 1.0 * deltax / deltay;
		yIncreation = 1;
	}
	x1 = start.x;
	y1 = start.y;
	for (int i = 0; i < num; i++) {
		point.x = (int)(x1 + 0.5);
		point.y = (int)(y1 + 0.5);
		switch (flag) {
		case 1:
			pDC->SetPixelV(point, RGB(255, 0, 0));
			break;
		case 2:
			point.x = 2 * start.x - point.x;
			pDC->SetPixelV(point, RGB(255, 0, 0));
			break;
		case 3:
			point.x = 2 * start.x - point.x;
			point.y = 2 * start.y - point.y;
			pDC->SetPixelV(point, RGB(255, 0, 0));
			break;
		case 4:
			point.y = 2 * start.y - point.y;
			pDC->SetPixelV(point, RGB(255, 0, 0));
			break;
		}
		x1 += xIncreation;
		y1 += yIncreation;
	}
}
void paintLine(CPoint start, CPoint end, CDC * pDC) {
	int deltax = end.x - start.x;
	int deltay = end.y - start.y;
	if (deltax > 0 && deltay > 0) {
		paintline(start, end, pDC, deltax, deltay, 1);
	}
	else if (deltax < 0 && deltay>0) {
		paintline(start, end, pDC, deltax, deltay, 2);
	}
	else if (deltax < 0 && deltay < 0) {
		paintline(start, end, pDC, deltax, deltay, 3);
	}
	else if (deltax > 0 && deltay < 0) {
		paintline(start, end, pDC, deltax, deltay, 4);
	}

}

void Ctest2Dlg::OnLButtonDown(UINT nFlags, CPoint point)
{
	if (!flag) {
		start = point;
		flag = !flag;
	}
	else {
		end = point;
		flag = !flag;
		CDC* pDC = GetDC();
		paintLine(start, end, pDC);
		ReleaseDC(pDC);
	}
	CDialogEx::OnLButtonDown(nFlags, point);
}

中点画线法

算法原理

与DDA算法不同中点画线法的主要思想是这样的,对于任意一条直线来说这条直线会将平面分成上下两部分,将直线上方的点代入到直线方程中会得到大于0的结果,将直线下方的点代入直线方程中会得到小于0的结果。
在这里插入图片描述
也就是说如果我们代入两个像素的中间值,会得到大于0或小于0的结果,当大于0时我们需要选取直线下方的点作为我们的选取的点,当代入小于0的时候我们需要选取上方的点作为我们选取的点。
我们先推导0<=k<=1时选取点的条件:
设方程为 y = k ∗ x + b y = k*x+b y=kx+b假设现在选取的点为 ( x , y ) (x, y) (x,y)则下一点需要在 ( x + 1 , y + 0 ) (x+1, y+0) (x+1,y+0) ( x + 1 , y + 1 ) (x+1, y+1) (x+1,y+1)做出选择,此时我们将 ( x + 1 , y + 0.5 ) (x+1, y+0.5) (x+1,y+0.5)代入得到 d = y + 0.5 − k ∗ ( x + 1 ) − b d = y+0.5-k*(x+1)-b d=y+0.5k(x+1)b此时d>0 或 d<0

  1. 当d>0时选择(x+1, y)点
    下面那一点就从 ( x + 2 , y + 0 ) (x+2, y+0) (x+2,y+0) ( x + 2 , y + 1 ) (x+2, y+1) (x+2,y+1)之中选择,将 ( x + 2 , y + 0.5 ) (x+2, y+0.5) (x+2,y+0.5)代入,$\d_{next} = y+0.5 - k*(x+1)-b\$ d = y + 0.5 − k ∗ ( x + 2 ) − b d = y+0.5-k*(x+2)-b d=y+0.5k(x+2)b
    d n e x t = d + 0.5 − k d_{next} = d+0.5-k dnext=d+0.5k
    d n e x t − d = − k d_{next} - d = -k dnextd=k
  2. 当d<0时选择(x+1, y+1)点.
    下面那一点就从 ( x + 2 , y + 1 ) (x+2, y+1) (x+2,y+1) ( x + 2 , y + 2 ) (x+2, y+2) (x+2,y+2)之中选择,将 ( x + 2 , y + 1.5 ) (x+2, y+1.5) (x+2,y+1.5)代入,$\d_{next} = y+1.5 - k*(x+1)-b\$ d = y + 0.5 − k ∗ ( x + 2 ) − b d = y+0.5-k*(x+2)-b d=y+0.5k(x+2)b
    d n e x t = d + 1 − k d_{next} = d+1-k dnext=d+1k
    d n e x t − d = 1 − k d_{next} - d = 1-k dnextd=1k
    我们知道了增量是如何变化的还不够,我们需要求出初始d的值,
    d = y − k ∗ x + b d = y-k*x+b d=ykx+b,因为初始点 ( x 0 , y 0 ) (x_0, y_0) (x0,y0)在直线上将点(x_0+1, y_0+0.5)代入即可求出初始d。
    d = 0.5 + k;
    因为k>1的情况与0<=k<=1可以通过对称得到,所以对于k>1的情况我们将所有的x换成y,所有的y换成x就能得到k>1情况下的中点画线法。

减少浮点运算

因为增量与-k或0.5-k有关,这种是可能涉及到浮点运算的,为了避免浮点运算,我们通过*2 Δ x \Delta x Δx来将浮点运算转换为整数运算。
此时对于所有过程我们都要乘上2 Δ x \Delta x Δx
由此得到初始 d = Δ x − 2 ∗ Δ y d = \Delta x - 2*\Delta y d=Δx2Δy
相应的d的增量变成 − 2 ∗ Δ y -2*\Delta y 2Δy 2 ∗ Δ x − 2 ∗ Δ y 2*\Delta x - 2*\Delta y 2Δx2Δy

算法实现

算法的实现与上面DDA算法类似。
代码如下:

void paintline(CPoint start, CPoint end, CDC* pDC, int deltax, int deltay, int flag) {
	int num;
	int d;
	CPoint point, point2;
	CPoint end2;
	switch (flag) {
	case 1:
		end2.x = end.x;
		end2.y = end.y;
		break;
	case 2:
		end2.x = 2 * start.x - end.x;
		end2.y = end.y;
		break;
	case 3:
		end2.x = 2 * start.x - end.x;
		end2.y = 2 * start.y - end.y;
		break;
	case 4:
		end2.y = 2 * start.y - end.y;
		end2.x = end.x;
		break;
	}
	deltax = end2.x - start.x;
	deltay = end2.y - start.y;
	num = deltax > deltay ? deltax : deltay;
	if (deltax >= deltay) {
		d = deltax - 2 * deltay;
	}
	else {
		d = deltay - 2 * deltax;
	}
	point.x = start.x;
	point.y = start.y;
	for (int i = 0; i < num; i++) {
		switch (flag) {
		case 1:
			point2.x = point.x;
			point2.y = point.y;
			pDC->SetPixelV(point2, RGB(255, 0, 0));
			break;
		case 2:
			point2.x = 2 * start.x - point.x;
			point2.y = point.y;
			pDC->SetPixelV(point2, RGB(255, 0, 0));
			break;
		case 3:
			point2.x = 2 * start.x - point.x;
			point2.y = 2 * start.y - point.y;
			pDC->SetPixelV(point2, RGB(255, 0, 0));
			break;
		case 4:
			point2.y = 2 * start.y - point.y;
			point2.x = point.x;
			pDC->SetPixelV(point2, RGB(255, 0, 0));
			break;
		}
		if (d > 0) {
			if (deltax >= deltay) {
				point.x += 1;
				d = d - 2 * deltay;
			}
			else {
				point.y += 1;
				d = d - 2 * deltax;
			}
			
		}
		else {
			point.x += 1;
			point.y += 1;
			if (deltax >= deltay) {
				d = d + (2 * deltax - 2 * deltay);
			}
			else {	
				d = d + (2 * deltay - 2 * deltax);
			}
		}
	}
}
void paintLine(CPoint start, CPoint end, CDC* pDC) {
	int deltax = end.x - start.x;
	int deltay = end.y - start.y;
	if (deltax > 0 && deltay > 0) {
		paintline(start, end, pDC, deltax, deltay, 1);
	}
	else if (deltax < 0 && deltay>0) {
		paintline(start, end, pDC, deltax, deltay, 2);
	}
	else if (deltax < 0 && deltay < 0) {
		paintline(start, end, pDC, deltax, deltay, 3);
	}
	else if (deltax > 0 && deltay < 0) {
		paintline(start, end, pDC, deltax, deltay, 4);
	}

}

这些代码都可以进行一下优化,这样能够提高效率,大家可以自行去优化。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1268636.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【计算机网络】虚拟路由冗余(VRRP)协议原理与配置

目录 1、VRRP虚拟路由器冗余协议 1.1、协议作用 1.2、名词解释 1.3、简介 1.4、工作原理 1.5、应用实例 2、 VRRP配置 2.1、配置命令 1、VRRP虚拟路由器冗余协议 1.1、协议作用 虚拟路由冗余协议(Virtual Router Redundancy Protocol&#xff0c;简称VRRP)是由IETF…

iMazing是什么软件?2024最新版本如何下载

iMazing是一款功能强大的iOS设备管理软件&#xff0c;它可以帮助用户备份和管理他们的iPhone、iPad或iPod Touch上的数据。除此之外&#xff0c;它还可以将备份数据转移到新的设备中、管理应用程序、导入和导出媒体文件等。本文将详细介绍iMazing的功能和安全性&#xff0c;并教…

【上海大学数字逻辑实验报告】二、组合电路(一)

一、 实验目的 熟悉TTL异或门构成逻辑电路的基本方式&#xff1b;熟悉组合电路的分析方法&#xff0c;测试组合逻辑电路的功能&#xff1b;掌握构造半加器和全加器的逻辑测试&#xff1b;学习使用可编程逻辑器件的开发工具 Quartus II设计电路。 二、 实验原理 异或门是数字…

Python-pip配置国内镜像源,快速下载包

文章目录 国内镜像源临时使用永久配置添加环境变量Path测试关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源码合集①Python工具包②Python实战案例③Python小游戏源码五、面试资料六、Python兼职渠道 国内…

Java数据结构之《栈实现括号匹配的检验》问题

一、前言&#xff1a; 这是怀化学院的&#xff1a;Java数据结构中的一道难度中等的一道编程题(此方法为博主自己研究&#xff0c;问题基本解决&#xff0c;若有bug欢迎下方评论提出意见&#xff0c;我会第一时间改进代码&#xff0c;谢谢&#xff01;) 后面其他编程题只要我写完…

黑马头条登陆功能详述

基于session的短信登陆&#xff1a; 发送验证码、短信验证码登陆、注册在后端&#xff0c;校验登陆在springmvc的连接器中&#xff0c;根据请求携带cookie来确定找到session 短信验证登陆与注册新用户&#xff1a; /*** 发送验证码*/Overridepublic Result sendCode(String ph…

Spark on yarn 模式的安装与部署

任务描述 本关任务&#xff1a; Spark on YARN 模式的安装与部署。 相关知识 为了完成本关任务&#xff0c;你需要掌握&#xff1a; Spark 部署模式的种类&#xff1b;Spark on YARN 模式的安装。 Spark 部署模式 Spark 部署模式主要分为以下几种&#xff0c;Spark Stand…

基于LangChain实现的知识库问答工具Langchain-Chatchat

基于embeddingLangChainChatGLM2-6B 构建行业知识库 Langchain-Chatchat LangChain 中文文档 langchain 本文使用的Langchain-Chatchat版本是0.2.7 一、构建垂类行业知识库的两种方案 方案一&#xff1a;使用开源LLM本地部署和微调 优点&#xff1a;数据最安全&#xff0c…

Java第二十章总结

一、线程简介 1.什么是进程&#xff1a; 进程是程序的运行过程&#xff0c;是系统进行资源分配和调度的一个独立单位。通俗来讲&#xff0c;进程就是在操作系统中运行的程序&#xff0c;例如&#xff1a;电脑中运行的微信、eclipse、idea等。 2.什么是线程 线程是操作系统能…

目标检测——R-CNN算法解读

论文&#xff1a;Rich feature hierarchies for accurate object detection and semantic segmentation 作者&#xff1a;Ross Girshick, Jeff Donahue, Trevor Darrell, Jitendra Malik 链接&#xff1a;https://arxiv.org/abs/1311.2524 代码&#xff1a;http://www.cs.berke…

不会PS!超简单的制作产品册方法

​产品册是展示产品的重要工具&#xff0c;对于很多企业来说&#xff0c;制作一本精美的产品册是必不可少的。但是&#xff0c;对于一些不会PS的人来说&#xff0c;制作产品册可能会觉得非常困难。其实&#xff0c;制作产品册并不需要PS等专业工具&#xff0c;只需要一些简单的…

电巢直播|揭秘FCBGA先进封装基板兴力量ze

随着2022年底ChatGPT的问世&#xff0c;我们不仅见证了从互联网时代到AI应用时代的跨越&#xff0c;也迎来了一个数据流量不断攀升的新纪元。在这个以数据为核心的新时代&#xff0c;算力网络成为支撑巨大数字经济的基石&#xff0c;其背后则是对硬件性能持续提升的迫切需求。 …

重点在正负上如何描述?看CHAT有何见解

问CHAT&#xff1a;重点在正负上如何描述&#xff1f; CHAT回复&#xff1a;在描述数据的波动情况时&#xff0c;也可关注其正负方向的变化。通常有以下几种方式&#xff1a; 1. 均值&#xff08;Mean&#xff09;&#xff1a;在一众数值中&#xff0c;如果大部分数值是正值且…

【数值计算方法(黄明游)】常微分方程初值问题的数值积分法:欧拉方法(向后Euler)【理论到程序】

文章目录 一、数值积分法1. 一般步骤2. 数值方法 二、欧拉方法&#xff08;Euler Method&#xff09;1. 向前欧拉法&#xff08;前向欧拉法&#xff09;2. 向后欧拉法&#xff08;后向欧拉法&#xff09;a. 基本理论b. 算法实现 常微分方程初值问题的数值积分法是一种通过数值方…

ISCTF2023 部分wp

学一年了还在入门( web where_is_the_flag ISCTF{41631519-1c64-40f6-8dbb-27877a184e74} 圣杯战争 <?php // highlight_file(__FILE__); // error_reporting(0);class artifact{public $excalibuer;public $arrow;public function __toString(){echo "为Saber选择…

实施工程师运维工程师面试题

Linux 1.请使用命令行拉取SFTP服务器/data/20221108/123.csv 文件&#xff0c;到本机一/data/20221108目录中。 使用命令行拉取SFTP服务器文件到本机指定目录&#xff0c;可以使用sftp命令。假设SFTP服务器的IP地址为192.168.1.100&#xff0c;用户名为username&#xff0c;密…

【爬虫实战】最新python豆瓣热榜Top250

一.最终效果 豆瓣是大多数新手练习爬虫的 二.数据定位过程 对于一个目标网站&#xff0c;该如何快速判定页面上的数据来源&#xff1f;首先你需要简单web调试能力&#xff0c;对大多数开发者来说都chrome浏览器应该是不二选择&#xff0c;当然我选中的也是。F12打开调试面板&…

Vite 了解

1、vite 与 create-vite 的区别 2、vite 解决的部分问题 3、vite配置文件的细节 3.1、vite语法提示配置 3.2、环境的处理 3.3、环境变量 上图补充 使用 3.4、vite 识别&#xff0c;vue文件的原理 简单概括就是&#xff0c;我们在运行 npm润dev 的时候&#xff0c;vite 会搭起…

springboot+mysql实现就业信息管理系统

springbootmysql实现的就业信息管理系统,有普通用户和管理员两种角色,演示地址:yanshi.ym4j.com:8091 普通用户账号&#xff1a;test 密码&#xff1a;123456管理员账号&#xff1a;admin 密码&#xff1a;123456 共包含就业信息管理、就业统计、用户管理等功能。 登录 就业信…

网络通信概述

文章目录 IP地址端口号协议三要素作用 五元组协议分层OSI七层模型TCP/IP 五层模型应用层传输层网络层数据链路层物理层 封装和分用发送方 - 封装中间转发接收方 - 分用 一般认为计算机网络就是利用通信线路和通信设备将地理上分散的、具有独立功能的多个计算机系统按不同的形式…