多边形的填充算法的分析

多边形的填充

分析

思路一

我们之前已经实现了对直线的扫描转换,但是现在我们遇到了新的问题那就是如何对多边形进行填充,如图所示,如何对图示的多边形进行填充呢?
在这里插入图片描述

我所想到的第一个办法是这样的,那就是假设上顶点为a,左顶点为b,右顶点为c,图示可以变成下面的图:
在这里插入图片描述

其中ab边,bc边,cd边所在的直线就分别可以使用下面的式子进行表示:
a b 边: A 1 ∗ x + B 1 ∗ y + C 1 = 0 ab边:A_1*x + B_1*y + C_1=0 ab边:A1x+B1y+C1=0
b c 边: A 2 ∗ x + B 2 ∗ y + C 2 = 0 bc边:A_2*x + B_2*y + C_2=0 bc边:A2x+B2y+C2=0
c a 边: A 3 ∗ x + B 3 ∗ y + C 3 = 0 ca边:A_3*x + B_3*y + C_3=0 ca边:A3x+B3y+C3=0
对于这三条边我们可以进行划分:
上边的两个点决定的边叫做上边,下边的两个点决定的边叫做下边,然后右边的两个点决定的边叫做右边。
如果一个点在三角形内,必然要满足此点在上边之下,下边之上,右边之左。也就是说将此点代入三个直线方程,上边的直线方程值小于0,右边的直线方程的值小于0,下边的直线方程的值大于0.
如图所示:
在这里插入图片描述

但是现在的方法有个弊端,他要求多个直线方程,还要判断点与直线之间的关系,在填充三角形的时侯我们还可以判断点与直线的关系,但当是四边形或五边形等更复杂的图形时就不适用了,为此我们需要寻求更好的算法。

思路二

现在既然求直线然后通过判断点与直线间满足的关系这种方法失败了,那么我们能不能构想一个更好的方法呢?
我们知道在点阵表示多边形时,由于是使用多边形内部的像素来刻画多边形的,所以必须得像素的中心处于多边形的内部,才能将此像素用于多边形的表示但是这一种表示损失了如顶点、几何边界等重要的信息。
我们可以看到在一行之中绘制在多边形内的像素一定在假设有x轴与边界的交点用红色表示出来:
在这里插入图片描述

除去边界点则用多边形内部的点表示多边形如下:
在这里插入图片描述

红色区域所表示的就是多边形的内部区域。
由此我们就能发现多边形内部的点的表示规则了。我们只看一行
在这里插入图片描述

发现多边形内部的点就是由取定一个y值,然后再由此y值得到对应边的交点,在交点之间的像素就是应该绘制的像素点。
由于由y=c确定的一条直线是平行于x轴的直线,所以此方法又叫做x-扫描线法。
那么此规则对于边数超过3的多边形还适用吗?
如下图所示:
在这里插入图片描述

此时多边形内部的点为:
在这里插入图片描述

我们此时注意到这一行:
在这里插入图片描述

别的行都是两两左右进行配对这一行由于中间两条边的交点重合了变成了三个点,但是此时仍要进行两两配对,所以我们是不是可以定义一个数组,然后求边界的像素,对于边界像素,以其y值作为数组的下标(将y值做一个变换,使其可以作为数组的下标)由此,在同一行的两个边界点一定是在二维数组的同一行,因为我们将y值作为作为与数组下标有关的量,同时因为每一条边都从起点求到终点,所以当起点与终点重合时也会记录,所以就不用担心有边界点重合的问题了。
但是我们看图会发现新的问题,那就是:
在这里插入图片描述

箭头所指的这几行,按理来说在求出边界点的时候会求出两个,但是边界我们是只需要一个的,所以这里我们是需要进行处理的。
此点后续再讨论
至此我们已经得出了x-扫描线法的所有分析,但是这一个算法我们是感觉有点差劲的,所以我们能不能得到更加高效的算法呢?

已知边界->种子填充法

我们现在已经知道多边形的边界了,在边界内部的点一定是属于多变形的点,而且在多边形内部的点的相邻的点一定属于多边形内部的点或者多边形的边,如下图所示:
在这里插入图片描述

图中红色的点属于多边形,与红色点相邻的绿色点(4-邻域)也属于多边形,绿色点的邻接点也属于多边形以此类推,我们就能遍历多边形的内部,这样我们就能将多边形绘制出来。
这种就叫做种子填充法,我们知道我们填充一个像素点时接下来要填充的就是它的四个邻接点,然后是邻接点的临接点,一次类推,这类似于队列的结构,当然也可以使用栈这种数据结构来实现,这取决于个人喜好。
但是我们现在要抉择的是,是在像素点入队的时候进行像素点的绘制,还是在出队的时候进行像素点的绘制,我们简单分析一下就会得出,大致每出队一个元素就将入队四个元素,入队数大于出队数,很有可能将已经入队的元素再次入队,如果我们不在入队的时候进行像素点的绘制,我们就会因为无法判断对应像素点的是否已经绘制过而将其再次入队。
经过上面的分析,我们对于种子填充发已经有了十足的了解:
可以写出代码如下:

struct Queue {int index_x;int index_y;struct Queue* next;
};
void Ctest3Dlg::OnBnClickedButton4() // 种子填充法进行多边形的绘制
{// TODO: 在此添加控件通知处理程序代码char* flag = (char*)malloc(sizeof(char) * 800 * 800);for (int i = 0; i < 800 * 800; i++) {flag[i] = 0;}auto startTime = std::chrono::high_resolution_clock::now();CString str;for (int i = 1; i < count; i++) {CDC* pDC = GetDC();paintLine(p[i - 1], p[i], pDC, flag); // 确定边界ReleaseDC(pDC);}CDC* pDC = GetDC();paintLine(p[count - 1], p[0], pDC, flag); // 确定边界struct Queue* head = (struct Queue*)malloc(sizeof(struct Queue));head->next = (struct Queue*)malloc(sizeof(struct Queue));struct Queue* front, * rear;front = head;rear = front->next;rear->next = NULL;int mark = 0;for (int i = down+1; i <= up; i++) {  //寻找一个合适的种子mark = 0;for (int j = left+1; j <= right; j++) {if (flag[i * 800 + j]) {mark = 1;}else {if (mark&&flag[i*800+j]==0) {head->index_x = i;head->index_y = j;mark += 1;}}if (mark == 2) {break;}}if (mark == 2) {break;}}CPoint paint;paint.x = head->index_x;paint.y = head->index_y;pDC->SetPixelV(paint, RGB(255, 0, 0));while (front != rear) {int xx = front->index_x;int yy = front->index_y;front = front->next;free(head);head = front;if (flag[(xx - 1) * 800 + yy] == 0) {rear->index_x = xx - 1;rear->index_y = yy;CPoint paint;paint.x = rear->index_y;paint.y = rear->index_x;rear->next = (struct Queue*)malloc(sizeof(struct Queue));rear = rear->next;rear->next = NULL;pDC->SetPixelV(paint, RGB(255, 0, 0));flag[(xx - 1) * 800 + yy] = 1;}if (flag[(xx + 1) * 800 + yy] == 0) {rear->index_x = xx + 1;rear->index_y = yy;CPoint paint;paint.x = rear->index_y;paint.y = rear->index_x;rear->next = (struct Queue*)malloc(sizeof(struct Queue));rear = rear->next;rear->next = NULL;pDC->SetPixelV(paint, RGB(255, 0, 0));flag[(xx + 1) * 800 + yy] = 1;}if (flag[xx * 800 + yy + 1] == 0) {rear->index_x = xx;rear->index_y = yy + 1;CPoint paint;paint.x = rear->index_y;paint.y = rear->index_x;rear->next = (struct Queue*)malloc(sizeof(struct Queue));rear = rear->next;rear->next = NULL;pDC->SetPixelV(paint, RGB(255, 0, 0));flag[xx * 800 + yy + 1] = 1;}if (flag[xx * 800 + yy - 1] == 0) {rear->index_x = xx;rear->index_y = yy - 1;CPoint paint;paint.x = rear->index_y;paint.y = rear->index_x;rear->next = (struct Queue*)malloc(sizeof(struct Queue));rear = rear->next;rear->next = NULL;pDC->SetPixelV(paint, RGB(255, 0, 0));flag[xx * 800 + yy - 1] = 1;}}auto endTime = std::chrono::high_resolution_clock::now();std::chrono::duration<double, std::milli> duration = endTime - startTime;str.Format(_T("%lf"), duration.count());time2.SetWindowText(str);ReleaseDC(pDC);
}

运行结果截图:
鼠标取点绘制:(注意我在绘制多边形的时候先绘制了多边形的边界,这样做的原因有两个:一是为了让整个多边形生成的时候有一个好的演示效果,在种子填充法中边界点是不需要绘制的, 二是在求边界的时候绘制很方便,在边表法中我们是边求交边绘制,如果要事先绘制边界就不太方便)
在这里插入图片描述

坐标输入:(出于与上面相同的原因,同样先绘制边界点)
在这里插入图片描述

x-扫描线法

通过上面的分析我们已经知道如果先进行直线的扫描转换再进行求交就可能会造成交于一条直线同一点的两个位置,从而在两两配对时左边界无法与右边界进行配对从而导致中间的部分扫描不到,但是这种问题我们要如何进行解决呢?
我们可以在扫描边界时对边界做一个取舍,这样我们就能唯一选中一个点作为边界,但是这一种方法可能会造成图形的走样,如下:
在这里插入图片描述
对于下面这一个图形如果选择最右侧作为边界就会造成多边形多出一些像素,如果选择最右边的点作为边界又会少一些像素,为此我们可以对这一些像素做一个取中处理,这样我们就能得到一个较为合适的像素点作为边界点。
同样的我们可以使用下面的方法进行取边界点,我们此时不是从边的像素点中取边界点而是采用先使用x-扫描线与边进行相交,然后得到一个坐标值,对坐标值进行取整得到一个像素点的位置,使用此位置作为多边形的绘制边界,此时我们可以论证这种取法,与我们上面的在一系列边界点取中得到的结果是相同的。
按照取中法应该在下面两个像素中选择一个像素点作为边界:
在这里插入图片描述

按照先扫描线相交然后进行取整得到像素点:
在这里插入图片描述

我们可以看到点的左右两个像素,就是我们取中时位于中间的两个像素。
这是因为,在进行取中时,我们其实是在长边的二分之一处进行抉择,在扫描线相交时是在短边的二分之一处进行抉择,根据三角形的相似性,中间的点也是一样的,由此我们就能从上述方法中选择一种来进行执行。
根据上面的两种不同的边界点的选取策略我们可以写出不同的代码。

有效的邻接边表法

经过上述的x-扫描线法分析,我们已经能够求出每一行的x扫描线的边界点,但是此时仍然存在问题,那就是提前算出每一条线上的边界点时,那么我们能不能在绘制的过程中将每一行的边界点绘制出来然后在边界点之间填充像素点呢?答案是肯定的,我们来看一下下面的图示:
在这里插入图片描述

我们发现只需要在顶点的位置存储边的信息,比如上图的y1位置存储两条边的顶点信息,然后y2中的信息是可以由y1中的信息加上斜率来推导出来的,所以我们只需要再顶点部位存放点的信息。
在这里插入图片描述

具体来说每一个结点需要我们存放什么信息呢?我们推导的前提是知道直线的增量对吧,所以结点中要放置增量,必须要有开始的位置,所以要放置起始点的坐标,因为y轴坐标是隐含的,所以只需要放置x的坐标就行了,同时我们也要放置直线结束的终点坐标,因为直线要结束,你不可能一直绘制下去。
故在边表的每一个节点中要放置以下三个数据。起始x数据, 增量数据, 终止y数据。
可以看出以此y值所在的平行于x轴的直线,这一点是两条边的顶点对吧,将y值下移,我们就可以得到发现相交的这两个点是上边顶点所在边的点,显然我们可以通过直线的扫描转换公式将这两个边界点确定下来,比如利用增量的DDA算法我们就可以知道当y变化1时x的变化量,依次递推我们就能在绘制的过程中依次确定边界点的位置。
大概如图所示的递推关系:
在这里插入图片描述

由y1求y2
接下来我们就分析,在同一行的两个坐标的我们可以按照x轴坐标递增的顺序安排整个边表的顺序,这样我们就能在配对的时候进行边界点的两两配对。
由此可以写出下述代码:

void Ctest3Dlg::OnBnClickedButton3()
{// TODO: 在此添加控件通知处理程序代码p[count] = p[0];CDC* pDC = GetDC();struct ListNode* list = (struct ListNode*)malloc(sizeof(struct ListNode) * 800);for (int i = 0; i < 800; i++) {list[i].next = NULL; // 对边表进行初始化}auto startTime = std::chrono::high_resolution_clock::now();CString str;for (int i = 0; i < count; i++) {CPoint* updot = p[i].y > p[i + 1].y ? &p[i] : &p[i + 1];  // 添加对应的顶点到CPoint* lowdot = p[i].y <= p[i + 1].y ? &p[i] : &p[i + 1];struct ListNode* s = NULL;int num = 0;for (int j = 0; j < count; j++) {s = &list[j];while (s->next != NULL) {s = s->next;if (updot->y == s->data.end) {num = 1;break;}}}struct ListNode* p = &list[updot->y - num];while (p->next != NULL) {p = p->next;}p->next = (struct ListNode*)malloc(sizeof(struct ListNode));p = p->next;p->next = NULL;p->data.start = updot->x;p->data.end = lowdot->y;p->data.increa = (updot->x - lowdot->x) * 1.0 / (updot->y - lowdot->y);if (num) {p->data.start = p->data.start - p->data.increa;}}// 创建边表;// 在创建的同时进行点的绘制for (int i = up - 1; i > down; i--) {struct ListNode* p = &list[i];while (p->next != NULL) {p = p->next;}struct ListNode* q = &list[i + 1];while (q->next != NULL) {q = q->next;if (q->data.end < i) {p->next = (struct ListNode*)malloc(sizeof(struct ListNode));p = p->next;p->next = NULL;p->data.start = q->data.start - q->data.increa;p->data.end = q->data.end;p->data.increa = q->data.increa;}}p = list[i].next;q = list[i].next->next;sort(list[i].next);while (q != p) {for (int j = p->data.start; j < q->data.start; j++) {CPoint paint;paint.x = j;paint.y = i;pDC->SetPixelV(paint, RGB(255, 0, 0));}if (q->next == NULL) {p = NULL;q = NULL;}else {p = p->next->next;q = q->next->next;}}}ReleaseDC(pDC);auto endTime = std::chrono::high_resolution_clock::now();std::chrono::duration<double, std::milli> duration = endTime - startTime;str.Format(_T("%lf"), duration.count());time1.SetWindowText(str);
}

程序演示:
鼠标取点:
在这里插入图片描述
坐标取点:
在这里插入图片描述

如果有什么地方讲的不好或者讲错的地方欢迎大家指出来,如果我所讲的对你们有帮助不要忘了点赞、收藏、关注哦! 我是你们的好伙伴apprentice_eye 一个致力于让知识变的易懂的博主。

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

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

相关文章

webRTC实时通信demo

参考文档&#xff1a; https://www.jianshu.com/p/f439ce5cc0be https://www.w3cschool.cn/socket demo流程示意图&#xff08;用户A向用户B推送视频&#xff09;&#xff1a; #mermaid-svg-0KZaDQ5DBl28zjmZ {font-family:"trebuchet ms",verdana,arial,sans-seri…

总结—elasticsearch启动失败的几种情况及解决

转载说明&#xff1a;如果您喜欢这篇文章并打算转载它&#xff0c;请私信作者取得授权。感谢您喜爱本文&#xff0c;请文明转载&#xff0c;谢谢。 摘要 本文主要梳理从ES初学以来所遇到的启动失败的几种情况。 1、使用root用户启动失败 在有一次搭建elasticsearch的时候&am…

【Linux】—— 匿名管道

前言&#xff1a; 接下来我将带大家探索 进程间通信 的方式。本期&#xff0c;要讲的就是管道其中之一“匿名管道”&#xff01;&#xff01; 目录 &#xff08;一&#xff09;进程间通信介绍 1、进程间通信目的 2、进程间通信发展 3、进程间通信分类 &#xff08;二&…

文件分片上传(模拟网盘效果)

文件分片上传&#xff08;模拟网盘效果&#xff09; 文章说明简单模拟拖拽文件夹和选择文件的进度条效果效果展示结合后端实现文件上传效果展示加上分片的效果效果展示加上MD5的校验&#xff0c;实现秒传和分片的效果后续开发说明源码下载 文章说明 文章主要为了学习文件上传&a…

2024年【黑龙江省安全员C证】考试及黑龙江省安全员C证找解析

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 2024年黑龙江省安全员C证考试为正在备考黑龙江省安全员C证操作证的学员准备的理论考试专题&#xff0c;每个月更新的黑龙江省安全员C证找解析祝您顺利通过黑龙江省安全员C证考试。 1、【多选题】下列属于编制安全检查…

浅聊配置化-要不要实现动态表单

1、配置化的原则 配置化是一种抽象&#xff0c;把事物分成2类&#xff1a;不变的&#xff0c;可变的。 如果事物都是可变的&#xff0c;是无法实现配置化的。 配置化的根本在于找到不变的事物&#xff0c;基于不变的事物进行可变事物的配置。 所以&#xff0c;认为一切皆可…

[LLM]大模型训练(二)--DeepSpeed使用

安装DeepSpeed与集成 DeepSpeed可以通过pip安装&#xff0c;无需指定PyTorch和CUDA的版本。DeepSpeed内包含需要自定义的CUDA算子&#xff0c;将通过即时编译的方式在运行时构建。 pip install deepspeed DeepSpeed与HuggingFace Transformers直接集成。使用者可以通过在模型…

从入门到精通,30天带你学会C++【第十一天:二分查找】

目录 Everyday English 前言 二分查找 例题 50分做法 分析利弊 示例代码 示例截图 100分做法 二分查找是什么&#xff1f; 这题该怎么用二分查找&#xff1f; 示例代码 示例截图 结尾 Everyday English Look before you leap. 三思而后行 前言 今天是2024年的…

爬取糖豆视频

爬虫案例积累&#xff0c;以爬取糖豆视频为例&#xff1a; 爬取视频类型的数据一般步骤&#xff1a; 1.点击media,刷新&#xff0c;播放一个视频&#xff0c;会刷新一个包&#xff0c;点击发现是播放视频的包&#xff0c; 2.复制这个包url中的关键字&#xff0c;在搜索框中进…

在宝塔Linux中安装Docker

前言 帮助使用宝塔的用户快速上手docke的安装 &#x1f4da;&#x1f4da; &#x1f3c5;我是默&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; ​​​​ &#x1f31f;在这里&#xff0c;我要推荐给大家我的专栏《Docker》。&#x1f3af;&#x1f3af…

[新版Hi3531DV200 性能强悍]

新版Hi3531DV200 性能强悍 Hi3531DV200是针对多路高清/超高清&#xff08;1080p/4M/5M/4K&#xff09;DVR产品应用开发的新一代专业SoC芯片。Hi3531DV200集成了ARM A53四核处理器和性能强大的神经网络推理引擎&#xff0c;支持多种智能算法应用。同时&#xff0c;Hi3531DV200还…

Spring Boot Admin健康检查引起的Spring Boot服务假死

问题现象 最近在spring boot项目中引入了 spring-boot-starter-actuator 后&#xff0c;测试环境开始出现服务假死的现象&#xff0c; 且这个问题十分怪异&#xff0c;只在多个微服务中的简称A的这个服务中出现&#xff0c;其他服务都没有出现这个问题&#xff0c; 之所以说…

按照故障码类型分类的API接口

随着汽车的普及&#xff0c;车辆故障也成为了一个不可忽视的问题。对于车主来说&#xff0c;及时了解故障码的含义以及解决方案十分重要。挖数据平台为解决这一问题&#xff0c;提供了一套按照故障码类型分类的API接口&#xff0c;用于查询车辆故障、故障码适用品牌以及提供相应…

STL——queue容器

1.queue基本概念 概念&#xff1a;queue是一种先进先出&#xff08;First In First Out,FIFO&#xff09;的数据结构&#xff0c;它有两个出口。 队列容器允许从一端新增元素&#xff0c;从另一端移除元素。 队列中只有队头和队尾才可以被外界使用&#xff0c;因此队列不允许…

Java API 操作Docker浅谈

背景&#xff1a; 使用com.github.docker-java库可以很方便地在Java中操作Docker。下面是一个详细的教程&#xff0c;包括创建镜像、创建容器、启动容器、停止容器和删除容器的步骤以及每一步的说明。 前提&#xff1a; 首先&#xff0c;在你的Java项目中添加com.github.doc…

Linux之组管理和权限管理

组的概念 如图所示&#xff1a;test.txt是由tom创建的&#xff0c;所以tom是文件的所有者&#xff0c;tom归属于组A&#xff0c;组A就是文件的所在组&#xff1b;组B就是文件的其他组。 所有者 谁创建了文件&#xff0c;谁就是文件的所有者。 查看文件的所有者 指令&…

Django 学习教程- Hello world入门案例

系列 Django 学习教程-介绍与安装-CSDN博客 欢迎来到第Djagno学习教程第二章Hello World 入门案例。 在本教程中&#xff0c;我将引导您完成django的Hello World入门案例。 让我们开始吧&#xff01; 版本 Django 5.0Python 3.10 创建项目 安装 Django 之后&#xff0…

信创之国产浪潮电脑+统信UOS Linux操作系统体验10:visual studio code中调试C++程序

☞ ░ 前往老猿Python博客 ░ https://blog.csdn.net/LaoYuanPython 一、引言 老猿在CSDN的《信创之国产浪潮电脑统信UOS操作系统体验2&#xff1a;安装visual studio code和cmake搭建C开发环镜》介绍了在国产浪潮电脑统信UOS操作系统中安装visual studio code和cmake搭建C开…

css动态传参,attr的妙用

今天再做一个编辑器的功能的时候&#xff0c;发现有一段非常奇妙的代码&#xff0c;使用attr获取div标签的data-label值。 css的attr?What fuck?这又是什么鬼东西&#xff0c;emmm。 查询后官方是这么回答的&#xff1a; CSS 表达式 attr() 用来获取选择到的元素的某一 HTM…

GO语言基础笔记(八):高级特性与性能优化

目录 反射&#xff08;Reflection&#xff09; 反射概念 反射的关键概念 反射的常见用途 代码示例 1. 检查类型和值 2. 修改变量值 3. 调用函数 4. 结构体反射 并发模式&#xff08;Concurrency Patterns&#xff09; 1. Worker Pool 模式 工作原理 在代码中的体现…