[内附完整源码和文档] 基于Qt实现的图形系统

一、概述
本系统拟完成一个图形系统,对多种常见图形进行基本操作

系统功能

二维图形的输入:可输入或全部清除不同颜色的直线、矩形、圆、椭圆、多边形、曲线、铅笔工具
二维图形的编辑:对于直线、矩形、圆、椭圆、多边形、曲线,可以通过鼠标拖拽标出的控制顶点来进行编辑
二维图形的剪裁:可通过拖拽编辑矩形的剪裁窗口对当前直线进行剪裁
二维图形的变换:在直线、矩形、圆、椭圆、多边形、曲线内部可通过鼠标拖拽进行平移,通过按钮进行左右旋转和翻转,以及缩放
二维图形的存储:可将图形存储在选择的路径下
三维模型的显示:可选择加载OFF文件,显示对应的三维模型,并通过鼠标拖动转换视角
环境说明

IDE:Qt Creator 4.8.0
Qt版本:Qt 5.4.0 (mingw491_32)
开发语言:C/C++
Debuggers:GNU gdb 7.8 for MinGW 4.9.1 32bit

二、算法介绍
系统包括的算法主要是二维图形的操作、三维模型的显示和用户操作的响应。

2.1 二维图形
2.1.1 基本图形
对于各种图形,虽然其具体的实现细节有所不同(主要体现在图形的绘制),但它们都有一些公共的属性和操作,比如计算图形的范围和中心点的位置,以及图形平移、旋转、缩放操作。这些属性和方法的算法是相同的。

  1. 图形范围
    对于直线、矩形、圆、椭圆、多边形、曲线,遍历其控制点的值,获得minX、maxX、minY、maxY。上述图形绘制后形成的所有点,不会有点的x值不在[minX, maxX]或y值不在[minY, maxY]中。四个值可以确定图形的范围,同时用于边框的绘制。

  2. 中心点
    中心点位置为(centerX, centerY),其中centerX = (minX + maxX) / 2,centerY = (minY + maxY) / 2。中心点可用做旋转、缩放的基准点。

  3. 平移
    图形的绘制依赖于控制点,因此只要更新每个控制点的信息即可。在图形平移方法中,传入的参数为x轴方向移动距离的值tx和y轴方向移动距离的值ty。对于(x, y)经平移(tx, ty)向量后的坐标为(x + tx, y + ty)。

  4. 旋转
    图形旋转传入的参数为角度数θ。控制点(x, y)顺时针旋转θ后,是以中心点(centerX, centerY)为基准的,坐标变为(centerX + (x - centerX) cosθ - (y - centerY) sinθ, centerY + (x - centerX) sinθ + (y - centerY) cosθ )。

  5. 缩放
    图形缩放传入的参数为在x方向上缩放比例为sx、在y方向上缩放比例为sy。控制点(x, y) 在x方向上缩放比例为sx、在y方向上缩放比例为sy后,坐标变为(x sx + centerX (1 - sx), y sy + centerY (1 - sy))。

2.1.2 直线
对于直线,只需要确定两个顶点的坐标作为控制点。直线生成有DDA算法、中点画线算法和Bresenham算法。

  1. 线生成DDA算法
    线生成的DDA算法,主要是在x和y中选择“变化得快”的方向,在这个方向上选取等距的一个个值,计算出每个值对应的另一方向的值。介于x和y的值都是整数,因此每次的“增量”至少为1,计算得到的另一个值往往不是整数,是需要四舍五入的,同时后者也可能是不精确的。如果选择“变化得慢”的方向作为基准,则每次迭代,另一方向上的变化幅度就会大于1,可以想象这种算法下点会比较“稀疏”,不如“稠密”的点组成的线精密。

具体来说,当直线的斜率(m)的绝对值大于1时,就是所谓的y轴“变化得快”,选取y轴进行取样,当斜率绝对值小于1时则要选取x轴。或者在实现上,可以直接比较两个顶点x轴上和y轴上的差值的绝对值,在差值绝对值较大的方向上取样,计算差值较小的方向上的取值。确定方向后可以得到两个方向上每次取样的增量,结果应该是取样方向上增量为1,计算方向上增量为[-1, 1]之间的值(绝对值为min( |m|, |1/m|))。

迭代计算出直线上每个点的位置。由于c++语言的特性,取值可以是加0.5再取整。部分伪代码如下:

dx=x2-x1;
dy=y2-y1;
e=(fabs(dx)>fabs(dy))?fabs(dx):fabs(dy);
dx/=e;
dy/=e;
for(int i=1;i<=e;i++)
{
SetPixel((int)(x+0.5), (int)(y+0.5));
x+=dx;
y+=dy;
}
2. 中点画线算法
根据直线的两个点,得到直线的方程f(x, y) = ax + by +c。取样方向的选择同DDA算法,下面讨论在x轴上取样、斜率为正的情况,y轴同理。

对于第i个选择的像素(xi ,yi),其下一位置的候选像素点为(xi+1, yi)和(xi+1, yi+1),取其中点(xi+1, y+1/2)代入直线方程,计算出来的决策参数为d = a * (xi+1) + b * (yi+1/2) +c。当d < 0时,中点是在直线的下方的,则靠上位置的(xi+1, yi+1)更靠近直线,如果d <= 0则选取(xi+1, yi)作为下个像素点。

根据上述方法进行迭代,至到达顶点。

  1. 直线生成的Bresenham算法
    Bresenham算法中对于两个候选的“下一个”像素点的选取是与中点画线算法相同的。△y和△x分别为线段端点的垂直和水平偏离量(整数)在第k步,决策参数为pk = 2△y • xk - 2△x • yk +c,第k+1步的决策参数为pk+1 = 2△y • xk+1 - 2△x • yk+1 +c,根据决策参数的符号来决定像素的选取。

  2. 直线的裁剪 - Cohen-Sutherland算法
    根据如图所示的方法对直线段的两个端点进行编码,得到两个编码结果进行比较。完全在窗口边界内的线段,两端点区域码均为0000;完全在裁剪矩形外的线段,对两个端点区域码进行逻辑与操作,结果不为0000;不能确定完全在窗口内外的线段,进行求交运算,求出线段与裁剪框的交点坐标,作为线段的新端点坐标。

2.1.3 矩形
矩形由四个点首尾相连而成。事实上,只需要对角线的两个控制点,就可以组合出四个顶点的信息。矩形的绘制利用2.1.2中实现的直线生成算法,绘制出四条直线。

2.1.4 圆
圆可以根据两个控制点形成的矩形来框定,选取了两个控制点x轴、y轴上差值较大的一个作为圆的直径。圆的函数为f(x, y) = x^2 + y2^2 - r^2。其画法可以看作是下述椭圆的特例,不再具体描述。只不过因为对称性,只需要画出1/8的部分,即只需要画出区域1,且区域1和区域2的分界点就是x = y的时候。

2.1.5 椭圆
椭圆可以依靠两个控制点形成的一个矩形来规定范围,生成的椭圆内切在其中。椭圆的圆心就是中心位置(centerX, centerY),x轴上的直径ra和y轴上的直径rb分别是两点x轴、y轴上差值的。

根据图形平移的理论,可以以原点为圆心计算出椭圆上的点(x, y),再变换成(x + centerX, y + centerY)。因为椭圆的对称性,可以在以原点为圆心的基础上,只进行第一象限的计算。椭圆的绘制采用中点椭圆生成算法。

中点椭圆生成算法
如图所示,定义椭圆函数f(x, y) = ry^2 * x^2 + rx^2 * y^2 - rx^2 * ry^2。以圆切线斜率绝对值为1作为划分,第一象限可以分为区域1和区域2。实际上,第一象限的切线斜率为负数,斜率dy/dx = -2ry^2 * x / (rx^2 * y),因此区域1进入区域2的条件是2ry^2 * x >= rx^2 * y。区域1中x轴“变化得快”,在x轴上取样,区域2则在y轴上取样。从(0, ry)或(rx, 0)开始,进行迭代,计算出椭圆上每个点的位置。下面指出区域1中的计算方法,区域2中同理。

椭圆上的点的决策参数的初始值为p0 = ry^2 - rx^2 * ry + rx^2 / 4。假设第k次选择的像素为(xk, yk),为确定下一次选择的像素,计算x(k+1)位置两个候选像素点的中点,即(xk+1, yk)和(xk+1, yk-1)的中点(xk+1, yk-1/2),将其代入椭圆函数求出决策参数。

若p1k < 0,则这个中点在圆内,(xk+1, yk)更接近真实的圆;若p1k >= 0,中点在不在圆内,选取(xk+1, yk-1)作为下一个像素点。迭代至循环结束,最后将第一象限的每个像素点映射到其余三个象限,再从原点将圆心移动到原本的中心位置。

2.1.6 多边形
多边形的生成也以直线生成为基础,利用直线的绘制将各个多边形的顶点作为控制点首尾相连起来。在实现上,多边形还需要一个额外的变量来记录控制点是否输入完全(首尾共点)。

2.1.7 曲线
曲线的绘制需要若干控制点,通过计算各个像素点的位置,将所有控制点按照顺序平滑地连接起来。曲线分为Bezier曲线和B样条曲线。

  1. Bezier曲线
    给定Pk=(xk,yk,zk),(k=0,1,2,…,n)共n+1个控制点,这些点混合产生位置向量BEZk,n(u)是Bernstein多项式,BEZi,n(u)=C(n,i)ui(1-u)n-i,其中C(n,i)=n!/[i!(n-i)!] (i=0,1,…,n) 。利用Bernstein基函数的降(升)阶公式,可使用递归计算得出Bézier曲线上点的坐标位置:BEZk,n(u)=(1-u)BEZk,n-1(u)+uBEZk-1,n-1(u),其中BEZk,k(u)=uk, BEZ0,k(u)=(1-u)k。

  2. B样条曲线
    给定n+1个控制顶点{Pi}(i=0,1,…,n),P0P1…Pn为控制多边形 n+k+2个参数节点向量:Un,k={ui|i=0,1,…,n+k+1,ui≤ui+1}。称如下形式的参数曲线P(u)为k+1阶(k次)B样条曲线:其中,Bi,k+1(u)为k+1阶(k次)B样条基函数。Bi,k+1(u)双下标中下标k+1表示k+1阶(k次)数、下标i表示序号。

2.1.8 填充区域
填充区域需要先有一个像素包围的封闭图形框架,然后计算出其内的所有像素点的位置。算法有扫描填充图元生成和区域填充图元生成。

  1. 扫描填充图元生成
    扫描填充的核心是“扫描”,一般从多边形的顶点出发,移动一条横越图形的扫描线,每次求交点,从左至右配对存储这些交点。扫描线每与两条线相交,这条线的交点表上就会有添加两个交点。需要处理共享顶点两条边位于扫描线同侧和异侧的情况。交点表的存储可以利用活化边表、排除不必要的求交测试的有序边表。

  2. 区域填充图元生成
    区域填充需要从一个连通的图形内的一个种子点开始,扩展到整个区域,直至遇到了边界。该算法比较适合实现“油漆桶”类似的功能。

由于区域填充有着“递归”的思想,递归的结束条件至关重要,即对于“遇到边界”的判断。在此用到了图形4连通和8连通的内容,对于像素4连通的区域,其边界像素只需要是8连通的,也可以是过约束的4连通边界;而8连通的区域,边界像素只能是4连通的,如果边界是8连通,则欠约束,内部的点会通过对角线位置上没有填充像素的边界的空缺,泛滥到边界之外,造成全局的填充。

此外,像素4连通区域定义的图形相对简单,8连通区域可以定义更复杂的图形,比如两个像素间只通过对角线形成连通的图形。

2.1.9 铅笔工具
铅笔工具的控制点是鼠标移动的轨迹,每个点都是控制点需要记录。为了保持轨迹的连贯性,可以在每两个距离很小的相邻点间用直线连接。

2.2 三维模型
取读OFF文件并显示其表示的三维模型,重点是在了解OFF文件存储的格式以及其表示的内容。

OFF文件第一行为“off”字符串,第二行是三个整数,分别表示了这个模型的点数量numV、面数量numF、边数量numE。接下来的numV行是点的信息,每行都是每个点的x、y、z值,值的定义域为[-1,1]的浮点数,同时也确定了点的编号(从0开始)。然后是numF行的面信息,每行第一个整数表示这个面拥有的点的数量,接下来的几个整数是点的编号。

在三维模型中,需要将面打印出来,也就是将每个面的顶点按顺序输出。

2.3 主界面
主界面是实现用户各种操作的接口,主要对于不同的鼠标事件进行响应。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

完整的源码和详细的文档,上传到了 WRITE-BUG技术共享平台 上,需要的请自取:

https://www.write-bug.com/article/2996.html

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 162,306评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,657评论 2 307
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,928评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,688评论 0 220
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,105评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 41,024评论 1 225
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,159评论 2 318
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,937评论 0 212
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,689评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,851评论 2 254
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,325评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,651评论 3 263
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,364评论 3 244
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,192评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,985评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,154评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,955评论 2 279