大学课程 | 计算机图形学,基于MFC和二维变换的画图软件

我终于肝完了计算机图形学的作业,记录一下我的报告

报告里面没有代码,不过上传到github了

  • Github链接🔗
  • Gitee链接🔗

基于MFC和二维变换的画图软件

摘 要

本文描述了二维复合变换的基本方法和思想,根据鼠标位置坐标获取起始点pStart和终止点pEnd的坐标,设计实现每个基本图形的画图方法,根据pStart和pEnd即可确定基本图形的控制点,进而绘制对应图形。规范化齐次坐标以后,图形几何变换可以表示为图形控制点点集合的规范化齐次坐标矩阵与二维变换矩阵相乘的形式,分别设置二维变换矩阵的参数信息,设计实现对应的方法,即可实现图形的二维变换功能。

设 计

“基于二维复合变换的动画制作软件”的设计中包括以下几个部分:(1) 程序结构设计,(2)鼠标消息映射,(3) 图形绘制实现,(4) 图形变换,(5)动画扩展实现,(6)信息保存,(7)程序交互设计。

1 程序总体结构

1.1 总体结构设计
1.1.1 绘图设计

基本图形包括点,直线,曲线,自由画笔,矩形,圆形,椭圆,三角形,左箭头,上箭头,五角星,四角形,五边形共12钟类型,每个基本图形都有自己的编号,用户在选择基本图形后,被选择图形的编号信息保存到dstyle变量中,绘图模块即可根据dstyle中的编号绘制相应的图形。图形大小,位置信息由全局变量pStart和pEnd控制,pStart和pEnd分别为用户在窗口内拖动鼠标时的起点坐标和终点坐标。根据两个坐标确定一个矩形,按照比例,设置相应的控制点,再根据控制点即可绘制相应图形。绘图流程图见图1.1。

1.1.2 变换设计

图形变换包括图形移动,图形旋转,图形放缩。绘图模块绘制图形结束后,会将pStart,pEnd,style等基本信息存入图表中。例如,选择旋转类型后,执行对应函数,将图表中所有图形的位置信息修改,再执行重绘函数,按照点表内容依次重绘变换之后的图形,即可实现图形的旋转变换。变换流程图见图1.2。

图1.1 绘图流程图

图1.2 变换流程图

2 程序实现

2.1 鼠标绘图的消息映射

为了实现基本图形的绘制和组合,需要在项目的视图View类中定义鼠标左键按下OnLButtonDown,鼠标移动OnMouseMove,以及鼠标左键抬起OnLButtonUp的消息映射,以实现拖动鼠标绘图功能。当鼠标左键按下时,设置一个变量为true保存绘图状态并且记录按下时的点,记为pStart,只有当该变量为true时,鼠标移动时才会将绘图,当鼠标左键抬起时,该变量赋值为false,并保存此时的点,记为pEnd。 其中,在鼠标左键按下并移动时,使用橡皮筋技术,即移动过程中选用画笔颜色取反模式(SetROP2(R2_NOT)),即可消除移动过程中不断绘制的图形,在鼠标左键抬起时,设置画笔为颜色覆盖模式(SETROP2(R2_COPYPEN)),绘制最终的图形,并保存pStart点和pEnd点,以及笔的粗细,形状,颜色等其他信息。

2.2 图形绘制实现
2.2.1 点

由于单个点的像素太小,不利于在图形绘制中使用与观察。这里使用了画一个微型填充圆的方法代替原始像素点。

2.2.2 直线

从直线起的以下图形的绘制均为根据外接矩形绘制内部图形。绘制图形时,当点击鼠标左键时获取矩形起点,按住不放拖动鼠标直至放开左键,放开鼠标左键的位置记录为矩形的终点。直线的绘制则根据矩形起始点使用MoveTo()和LineTo()函数绘制。

2.2.3 等腰和直角三角形

在使用鼠标拉取的矩形中选取点位置并用画线函数连接点实现。三角形包括3个顶点。拉取矩形的起点坐标为(pStart.x,pStart.y),终点坐标为(pEnd.x,pEnd.y)。根据两种三角形在矩形中绘制时的对应比例,等腰三角形3个顶点坐标分别为:

代码语言:javascript
复制
P1  (pStart.x+pEnd.x)/2,pStart.y);
P2  (pStart.x,pEnd.y);
P3  (pEnd.x,pEnd.y);

直角三角形的三个顶点坐标为:

代码语言:javascript
复制
P1  (pStart.x,pStart.y);
P2  (pEnd.x,pEnd.y);
P3  (pStart.x,pEnd.y);
2.2.4 矩形和填充矩形

在使用鼠标拉取的矩形中获取了起始点和终止点后用矩形函数实现。拉取矩形的起点坐标为(pStart.x,pStart.y),终点坐标为(pEnd.x,pEnd.y)。要绘制矩形由绘制矩形的函数实现pdc->Rectangle(pStart.x , pStart.y , pEnd.x, pEnd.y)。绘制填充矩形则在绘制前使用画刷以填充内部。

2.2.5 圆形和填充圆

在使用鼠标拉取的矩形中获取了起始点后。将两点间的距离作为要画圆的半径r。使用绘制圆函数进行绘制pdc->Ellipse(pStart.x-r,pStart.y-r , pStart.x+r , pStart.y+r)。绘制填充矩形则在绘制前使用画刷以填充内部。

2.2.6 自由画笔

在鼠标左键按下,并且移动的过程中,通过不断触发OnMouseMove消息映射,在移动中的点的位置和上一个位置间连线,即可实现自由画笔功能。

2.2.7 左箭头

在使用鼠标拉取的矩形中选取点位置并用画线函数连接点实现。左箭头包括7个顶点。拉取矩形的起点坐标为(pStart.x,pStart.y),终点坐标为(pEnd.x,pEnd.y)。根据左箭头在矩形中绘制时的对应比例,7个顶点坐标为:

代码语言:javascript
复制
P1  (pStart.x, pStart.y-dy/2);
P2  (pStart.x+dx/2),pStart.y);
P3  (pStart.x+dx/2), pStart.y-dy/4);
P4  (pEnd.x, pStart.y-dy/4);
P5  (pEnd.x, pStart.y-3*dy/4);
P6  (pStart.x+dx/2), pStart.y-3*dy/4);
P7  (pStart.x+dx/2),pEnd.y);

其中dy=pStart.y-pEnd.y;dx= pEnd.x-pStart.x

2.2.8 上箭头

在使用鼠标拉取的矩形中选取点位置并用画线函数连接点实现。上箭头包括7个顶点。拉取矩形的起点坐标为(pStart.x,pStart.y),终点坐标为(pEnd.x,pEnd.y)。根据上箭头在矩形中绘制时的对应比例,7个顶点坐标为:

代码语言:javascript
复制
P1  (pStart.x, pStart.y-dy0/2);
P2  ( (pStart.x+dx0/2,pStart.y);
P3  (pEnd.x, pStart.y-dy0/2);
P4  (pStart.x+3*dx0/4, pStart.y-dy0/2);
P5  (pStart.x+3*dx0/4,pEnd.y);
P6  (pStart.x+dx0/4,pEnd.y);
P7  (pStart.x+dx0/4, pStart.y-dy0/2);

其中dy0=pStart.y-pEnd.y;dx0=pEnd.x-pStart.x

2.2.9 五角星

在使用鼠标拉取的矩形中选取点位置并用画线函数连接点实现。五角星绘制包括5个顶点。拉取矩形的起点坐标为(pStart.x,pStart.y),终点坐标为(pEnd.x,pEnd.y)。根据五角星在矩形中绘制时的对应比例,5个顶点坐标为:

代码语言:javascript
复制
P1(pStart.x+pEnd.x)/2),pStart.y);
P2(pStart.x+RX*(sin(72*pi/180)cos(54*pi/180))/2/sin(72*pi/180)),pEnd.y);
P3(pEnd.x, pStart.y+RY*(1-cos(72*pi/180))/(1+sin(54*pi/180)));
P4(pStart.x, pStart.y+RY*(1-cos(72*pi/180))/(1+sin(54*pi/180)));
P5(pStart.x+RX*(sin(72*pi/180)+cos(54*pi/180))/2/sin(72*pi/180),pEnd.y);

其中pi = 3.1415926;RX = -(pStart.x-pEnd.x);RY = -(pStart.y-pEnd.y);

2.2.10 五边形

在使用鼠标拉取的矩形中选取点位置并用画线函数连接点实现。五边形包括5个顶点。拉取矩形的起点坐标为(pStart.x,pStart.y),终点坐标为(pEnd.x,pEnd.y)。根据五边形在矩形中绘制时的对应比例,5个顶点坐标为:

代码语言:javascript
复制
P1  (pStart.x+pEnd.x)/2,pStart.y);
P2  (pStart.x,(pEnd.y-pStart.y)*0.41+pStart.y);
P3  (pStart.x+(pEnd.x - pStart.x)*0.19,pEnd.y);
P4  (pStart.x+(pEnd.x - pStart.x)*0.81,pEnd.y);
P5  (pEnd.x, (pEnd.y-pStart.y)*0.41+pStart.y);
2.2.11 四角星

在使用鼠标拉取的矩形中选取点位置并用画线函数连接点实现。四角星包括8个顶点。拉取矩形的起点坐标为(pStart.x,pStart.y),终点坐标为(pEnd.x,pEnd.y)。根据四角星在矩形中绘制时的对应比例,8个顶点坐标为:

代码语言:javascript
复制
P1  ((pStart.x+pEnd.x)/2,pStart.y)
P2  (pStart.x+(pEnd.x-pStart.x)*3/8,(pEnd.y-pStart.y)* 3/8+pStart.y);
P3  (pStart.x,(pStart.y+pEnd.y)/2);
P4  (pStart.x+(pEnd.x-pStart.x)* 3/8,(pEnd.y-pStart.y)*6/8+pStart.y);
P5  ((pStart.x + pEnd.x)/2),pEnd.y);
P6  (pStart.x+(pEnd.x-pStart.x)* 6/8,(pEnd.y-pStart.y)*6/8+pStart.y);
P7  (pEnd.x,(pStart.y+pEnd.y)/2);
P8  (pStart.x+(pEnd.x-pStart.x)*6/8,(pEnd.y-pStart.y)*3/8+pStart.y);
2.2.12 弧线

在使用鼠标拉取的矩形中获取了起始点后,使用绘制椭圆弧线函数进行绘制

代码语言:javascript
复制
pdc->Arc(pStart.x,pStart.y,pEnd.x,pEnd.y,int((pStart.x+pEnd.x)/2),pStart.y,pEnd.x,int((pStart.y+pEnd.y)/2));

2.3 图形变换实现

2.3.1 图形移动

图形移动包括包括左移,右移和上移,下移,点表中存有每个图形的pStart点,pEnd点和其他样式信息,绘图函数可根据这些信息重新绘制对应图形,所以只要调用transform.tranlate()函数将pStart,pEnd的x,y坐标同时增加减少相同数值即可完成图形的上下左右移动。

2.3.2 图形旋转

图形旋转包括顺时针旋转和逆时针旋转。与其他的变换不同的是,旋转需要定义一个旋转中心,默认为坐标系原点。如果没有设置旋转中心,旋转变换可能会导致图形变换到窗口之外,所以设置坐标点(pStart+pEnd)/2为旋转中心,调用Transform.Rotate()函数,即可实现在原位置旋转变换。

2.3.3 图形放缩

图形放大和缩小是由pStart和pEnd坐标的等比变换实现的。每次放大,将pStart和pEnd的x,y坐标放大两倍,每次缩小将pStart和pEnd的x,y坐标设置为原来的1/2。经过多次放缩后,可能导致图形太大或者太小而不能正常显示的问题,所以每次放缩判断pStart和pEnd之间的距离,如果距离大于窗口距离,或距离小于5个像素则终止放缩并给出相应提示。

2.4 图形变换扩展
2.4.1 动画设计

通过自定义文本对话框类(Cchoosedig),实现通过输入框输入获取复合图形变换运动时间的功能,基于原有的图形变化函数,增加根据输入时间循环移动以及延时(Sleep())的功能,即实现了自定义动画时间的动画制作。

2.4.2 自定义点表结构

由于动画制作需要修改组合复杂图形的所有点的信息,因此需要遍历点集,再重绘所有图形,因此,自定义了一个结构体,用来存储每一个图形的信息,其中信息包括:起始点,终止点,图形类型,画笔类型,画笔粗细,画笔颜色,结构体如图2.1,然后为这个结构体创建链表,再修改文档类的串行化Serialize函数即可。

图2.1 自定义结构体

2.4.3 运动时间设置

为了自定义运动时间,采用了文本对话框,通过输入运动时间,从对话框获取信息,保存到变量,再传递到View类,实现动画制作功能。时间设置效果如图2.2所示。

图2.2 运动时间设置

2.4.4 图形重绘

对于图形重绘,先暂存当前所选择的图形类型,画笔,颜色等信息,再获取点表的长度,然后循环遍历点表,取出点表中的数据,赋值给CDC类的指针对象pdc,根据图形类型和其他信息画出所有对应的图形。最后恢复之前暂存的信息,即可实现图形重绘功能,且不影响当前选择的样式。

2.5 程序交互实现
2.5.1 绘图类型选择

通过点击菜单栏的图标按钮,如图2.3所示,可以设置绘制图形的类型。具体实现是,当按钮被点击,调用相应的响应函数设置dstyle,并设置cclick为false即可。

图2.3 菜单栏中选择绘图类型的按钮

2.5.2 画笔颜色选择

颜色设置是调用系统自带的颜色对话框(CColorDialog)完成对画笔、画刷颜色的选择,同时选用该对话框能够实现自定义颜色。颜色选择对话框如图2.4所示。

图2.4 颜色选择对话框

2.5.3 画笔类型选择

在菜单栏中,有画笔形状和画笔粗细可以选择。其中,画笔形状包含包含直线(PS_SOLID),点线(PS_DOT),虚线(PS_DASH),画笔粗细包括粗线,标准线和细线。根据选择的画笔类型,设置type和thickness的值即可。其中,画笔形状中的虚线和点线只有在画笔粗细为细线的时候才能正常显示,当画笔粗细为标准或者粗线时,画出来的都是实线。

2.5.4 清屏

在清屏时,首先会有弹窗提示是否确定清屏,点击“否”则取消操作,点击“是”则进行清屏。清屏功能的具体操作是先调用RedrawWindow()函数清屏,然后清空点表MyList并设置dstyle和cclick分别为初始值0和false即可。

2.5.5 回退

由于本项目把每个图形外接矩形的一对顶点保存在了点表MyList中的一个自定义的节点结构体中,所以在回退时,我们只需要删除点表中的最后一个节点,然后根据点表重新绘图即可。

3 程序运行效果

3.1 基本图形实现

设计实现了包含点,直线段,椭圆弧线,矩形,填充矩形,等腰三角形,直角三角形,椭圆,圆,填充圆,五边形,五角星,四角星,箭头等多种基础图形,并且实现画图以及选择画笔类型功能,初始窗口如图3.1所示,基础图形效果如图3.2所示。

图3.1 初始窗口

图3.2 基础图形效果

3.2 组合复杂图形以及整体变换

实现了基本图形组合成复杂图形的功能,并且具有回退,清空画布,颜色等功能,具有包含平移,旋转,放大缩小,输入动画时长的功能。组合复杂图形以及变换效果如图3.3所示。

图3.3 组合复杂图形及变换

4 结论

通过这次的计算机图形学实践,我们熟悉了计算机是如何利用算法来生成,处理和显示图形的,学习了如何通过使用Visual C++ 6.0编程环境的MFC框架进行计算机图形学的编程。在程序编写的过程中,我们掌握了很多MFC库所提供的类及其功能函数的使用方法,也根据项目的需要编写了很多自定义的结构体和功能函数。在实验的过程中,我们逐渐了解了MFC框架中,不同类的功能和定义方法,明白了双缓冲机制的原理,熟悉了基本的消息映射功能和对话框的设计,以及如何在不同类间传递数据的方法。并且,在动画制作的过程中,我们又进一步加强了对于二维变化的理解,知道了图形变化的本质还是数学计算。在老师的帮助和我们小组的努力下,我们的收获是如此巨大,我们相信在今后的学习生活中,图形学的原理知识将会给我们很大的帮助。