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

直线的扫描转换:

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);
	}

}

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

UE Web Remote Control

前言 最近在研究UE自启WEB服务和网页通信以此来通过网页与UE进行数据交互&#xff0c;这样最好的方式就是可以摒弃掉整个繁琐的通信连接流程如TCP UDP&#xff0c;但是找到的一些方法都不是很适用&#xff0c;尤其是WEBUI这个插件它只适合内嵌到UE本身才能完成交互&#xff0c;…

mybatis关于namespace以及id以及Mapper接口命名的说明(了解)

1、建库建表 CREATE DATABASE mybatis-example;USE mybatis-example;CREATE TABLE t_emp(emp_id INT AUTO_INCREMENT,emp_name CHAR(100),emp_salary DOUBLE(10,5),PRIMARY KEY(emp_id) );INSERT INTO t_emp(emp_name,emp_salary) VALUES("tom",200.33); INSERT INTO…

Pytorch从零开始实战11

Pytorch从零开始实战——ResNet-50V2算法实战 本系列来源于365天深度学习训练营 原作者K同学 文章目录 Pytorch从零开始实战——ResNet-50V2算法实战环境准备数据集模型选择开始训练可视化总结 环境准备 本文基于Jupyter notebook&#xff0c;使用Python3.8&#xff0c;Pyt…

CentOS 8 安装国内、本地YUM源

环境&#xff1a;windows 11、 VMware 17、Cent OS 8 目的&#xff1a;加快软件下载速度 1、国内YUM源安装 使用国外的源&#xff0c;速度卡到不显示 备份默认YUM源文件 [rootlocalhost ~]# cd /etc/yum.repos.d/ [rootlocalhost yum.repos.d]# mkdir yum.bak [rootlocalho…

Drawer抽屉(antd-design组件库)简单用法

1.Drawer抽屉 屏幕边缘滑出的浮层面板。 2.何时使用 抽屉从父窗体边缘滑入&#xff0c;覆盖住部分父窗体内容。用户在抽屉内操作时不必离开当前任务&#xff0c;操作完成后&#xff0c;可以平滑地回到原任务。 需要一个附加的面板来控制父窗体内容&#xff0c;这个面板在需要时…

3D场景建模工具

在线工具推荐&#xff1a; 三维数字孪生场景工具 - GLTF/GLB在线编辑器 - Three.js AI自动纹理化开发 - YOLO 虚幻合成数据生成器 - 3D模型在线转换 - 3D模型预览图生成服务 1. 什么是3D场景建模&#xff1f; 3D场景建模是一种通过计算机图形学技术&#xff0c;将现实世…

<Linux>冯诺依曼体系结构||操作系统||系统调用于用户操作接口

前言:本文从软硬件角度计算机解释软硬件结构 硬件—冯诺依曼体系结构 软件—操作系统 文章目录 冯诺依曼计算机体系结构背景理解举例 操作系统(OS)OS的管理为什么要有操作系统? 系统调用与用户操作接口系统调用用户操作接口引入:printf&&scanf的重新理解库函数 计算机…

Could NOT find resource [logback-test.xml]

修改 之后就可以正常启动了

Node.js案例 - 记账本

目录 项目效果 项目的搭建 ​编辑 响应静态网页 ​编辑 ​编辑 结合MongoDB数据库 结合API接口 进行会话控制 项目效果 该案例实现账单的添加删除查看&#xff0c;用户的登录注册。功能比较简单&#xff0c;但是案例主要是使用前段时间学习的知识进行实现的&#xff0c…

详解原生Spring当中的额外功能开发MethodBeforeAdvice与MethodInterceptor接口!

&#x1f609;&#x1f609; 学习交流群&#xff1a; ✅✅1&#xff1a;这是孙哥suns给大家的福利&#xff01; ✨✨2&#xff1a;我们免费分享Netty、Dubbo、k8s、Mybatis、Spring...应用和源码级别的视频资料 &#x1f96d;&#x1f96d;3&#xff1a;QQ群&#xff1a;583783…

软件测试测试文档的编写和阅读

在软件测试中的流程中&#xff0c;测试文档也是一个重要的流程&#xff0c;所以测试人员也需要学习测试文档的编写和阅读。 一、定义&#xff1a; 测试文档&#xff08;Testing Documentation&#xff09;记录和描述了整个测试流程&#xff0c;它是整个测试活动中非常重要的文…

layui提示框没有渲染bug解决

bug&#xff1a;使用layui时或许是依赖导入又或是ideal和浏览器缓存问题导致前面明明正常的页面显示&#xff0c;后面出现提示框没有css样式&#xff0c;弹出框没有背景css 效果如下 解决后 解决方法 在你的代码中引入layer.js 我这是jsp页面 <script type"text/jav…

idea方法注释模版设置

方法上面的注释模版&#xff1a; Template text: ** Description $desc$ $param$ $return$* Aauthor yimeng* date $DATE$ $TIME$ **/param&#xff1a; groovyScript("def result ;def params \"${_1}\".replaceAll([\\\\[|\\\\]|\\\\s], ).split(,).toLis…

机器学习笔记 - 3D数据的常见表示方式

一、简述 从单一角度而自动合成3D数据是人类视觉和大脑的基本功能,这对计算机视觉算法来说是比较难的。但随着LiDAR、RGB-D 相机(RealSense、Kinect)和3D扫描仪等3D传感器的普及和价格的降低,3D 采集技术的最新进展取得了巨大飞跃。与广泛使用的 2D 数据不同,3D 数据具有丰…

Opencv 极坐标变换

变换后图片 代码 // 以Center为极坐标原点&#xff0c;将RowFrom到RowTo的圆环&#xff0c;仅仅变换该范围内的点&#xff0c;忽略掉其他部分。 #include "polar_transeforme.hpp" #include <string>using namespace cv;void calculate_map(int rouFrom, int …

Matlab 在一个文件中调用另一个文件中的函数

文章目录 Part.I IntroductionPart.II 方法Chap.I A 文件中只有一个函数Chap.II A 文件中有多个函数 Part.I Introduction 本文介绍一下在脚本文件 B 中调用文件 A 中的函数的方法。 Part.II 方法 目的&#xff1a;在文件B.m调用A.m中的函数 默认两个文件在一个文件夹下&…

JSch线上出现com.jcraft.jsch.JSchException: channel is not opened.问题分析

JSch线上出现com.jcraft.jsch.JSchException: channel is not opened.问题分析 文章目录 JSch线上出现com.jcraft.jsch.JSchException: channel is not opened.问题分析1. 背景1.系统使用jsch这个框架做文件发送以及远程命令执行的操作,系统一直运行正常,直到某一个环境发现 2.…

陪诊系统:基于自然语言处理的患者沟通创新

医疗领域的数字化转型正日益引入创新技术&#xff0c;其中基于自然语言处理&#xff08;NLP&#xff09;的陪诊系统成为提升患者沟通的一项关键技术。本文将深入研究这一领域&#xff0c;介绍陪诊系统如何借助NLP实现患者沟通的创新&#xff0c;并提供一个简单的Python代码示例…

Excel导入组件的封装以及使用页面点击弹出该弹框

封装的组件 <template><el-dialogwidth"500px"title"员工导入":visible"showExcelDialog"close"$emit(update:showExcelDialog, false)"><el-row type"flex" justify"center"><div class&q…

如何跑通跨窗口渲染:multipleWindow3dScene

New 这是一个跨窗口渲染的示例&#xff0c;用 Three.js 和 localStorage 在同一源&#xff08;同产品窗口&#xff09;上跨窗口设置 3D 场景。而这也是本周推特和前端圈的一个热点&#xff0c;有不少人在争相模仿它的实现&#xff0c;如果你对跨窗口的渲染有兴趣&#xff0c;可…