图形
更新时间:2024-05-30 17:41:01 阅读量: 综合文库 文档下载
基本图形的生成
计算机图形学已成为计算机技术中发展最快的领域,计算机图形软件也相应得到快速发展。计算机绘图显示有屏幕显示、打印机打印图样和绘图机输出图样等方式,其中用屏幕显示图样是计算机绘图的重要内容。
计算机上常见的显示器为光栅图形显示器,光栅图形显示器可以看作像素的矩阵。像素是组成图形的基本元素,一般称为―点‖。通过点亮一些像素,灭掉另一些像素,即在屏幕上产生图形。在光栅显示器上显示任何一种图形必须在显示器的相 应像素点上画上所需颜色,即具有一种或多种颜色的像素集合构成图形。确定最佳 接近图形的像素集合,并用指定属性写像素的过程称为图形的扫描转换或光栅化。 对于一维图形,在不考虑线宽时,用一个像素宽的直、曲线来显示图形。二维图形 的光栅化必须确定区域对应的像素集,并用指定的属性或图案进行显示,即区域 填充。
复杂的图形系统,都是由一些最基本的图形元素组成的。利用计算机编制图形软件时,编制基本图形元素是相当重要的,也是必需的。点是基本图形,本章主要讲述如何在指定的输出设备(如光栅图形显示器)上利用点构造其他基本二维几何图形(如点、直线、圆、椭圆、多边形域及字符串等)的算法与原理,并利用Visual C++编程实现这些算法。 1.1 直 线
数学上,理想的直线是由无数个点构成的集合,没有宽度。计算机绘制直线是在显示器所给定的有限个像素组成的矩阵中,确定最佳逼近该直线的一组像素,并且按扫描线顺序,对这些像素进行写操作,实现显示器绘制直线,即通常所说的直线的扫描转换,或称直线光栅化。 由于一图形中可能包含成千上万条直线,所以要求绘制直线的算法应尽可能地快。本节介绍一个像素宽直线的常用算法:数值微分法(DDA)、中点画线法、Bresenham 算法。 1.1.1 DDA(数值微分)算法
DDA算法原理:如图1-1所示,已知过端点 的直线段 ;直线斜率为 ,从 的左端点 开始,向 右端点步进画线,步长=1(个像素),计算相应的 坐标 ;取像素点 [ , round(y)] 作为当前点的坐标。计算 ,当 ,即当x每递增1,y递增k(即直线斜率)。
1的情形。在这种情况下,x每增加1, y最多增加1。当?注意:上述分析的算法仅适用于k 时,必须把x,y地位互换,y每增加1,x相应增加1/k(请参阅后面的Visual C++程序)。 1.1.2 生成直线的中点画线法
中点画线法的基本原理如图1-2所示。在画直线段的过程中,当前像素点为P,下一个像素点有两种选择,点P1或P2。M为P1与P2中点,Q为理想直线与X=Xp+1垂线的交点。当M在Q的下方时,则P2应为下一个像素点;当M在Q的上方时,应取P1为下一点。 中点画线法的实现:令直线段为L[ p0(x0,y0), p1(x1, y1)],其方程式F(x, y)=ax+by+c=0。 其中,a=y0–y1, b=x1–x0, c=x0y1–x1y0;点与L的关系如下。 在直线上,F(x, y)=0; 在直线上方,F(x, y)>0; 在直线下方,F(x, y)<0。
把M代入F(x, y),判断F的符号,可知Q点在中点M的上方还是下方。为此构造判别
式d=F(M)=F(xp+1, yp+0.5)=a(xp+1)+b(yp+0.5)+c。 当d < 0,L(Q点)在M上方,取P2为下一个像素。 当d > 0,L(Q点)在M下方,取P1为下一个像素。 当d=0,选P1或P2均可,取P1为下一个像素。 其中d是xp, yp的线性函数。 1.1.3 Bresenham算法
Bresenham算法是计算机图形学领域使用最广泛的直线扫描转换算法。由误差项符号决定下一个像素取右边点还是右上方点。
设直线从起点(x1, y1)到终点(x2, y2)。直线可表示为方程y = mx+b,其中b=y1–mx1, m = (y2–y1)/(x2–x1)=dy/dx;此处的讨论直线方向限于第一象限,如图1-3所示,当直线光栅化时,x每次都增加1个单元,设x像素为(xi,yi)。下一个像素的列坐标为xi+1,行坐标为yi或者递增1为yi+1,由y与yi及yi+1的距离d1及d2的大小而定。计算公式为 y = m(xi + 1) + b (1.1) d1 = y – yi (1.2) d2=yi+1–y (1.3) 如果d1–d2>0,则yi+1=yi+1,否则yi+1=yi。
式(1.1)、(1.2)、(1.3)代入d1–d2,再用dx乘等式两边,并以Pi=(d1–d2),dx代入上述等式,得
Pi = 2xidy–2yidx+2dy+(2b–1)dx (1.4)
d1–d2是用以判断符号的误差。由于在第一象限,dx总大于0,所以Pi仍旧可以用做判断符号的误差。Pi+1为
Pi+1 = Pi+2dy–2(yi+1–yi)dx (1.5)
求误差的初值P1,可将x1、y1和b代入式(1.4)中的xi、yi,而得到 P1 = 2dy–dx
综述上面的推导,第一象限内的直线Bresenham算法思想如下:
(1)画点(x1, y1),dx=x2–x1,dy=y2–y1,计算误差初值P1=2dy–dx,i=1。 (2)求直线的下一点位置xi+1 = xi + 1,如果Pi>0,则yi+1=yi+1,否则yi+1=yi。 (3)画点(xi+1, yi+1)。
(4)求下一个误差Pi+1,如果Pi>0,则Pi+1=Pi+2dy–2dx,否则Pi+1=Pi+2dy。 (5)i=i+1;如果i 为编程实现上述算法,本程序利用最基本的绘制元素(如点、直线等),绘制图形。如图1-4所示,为程序运行主界面,通过选择菜单及下拉菜单的各功能项分别完成各种对应算法的图形绘制。 图1-4 基本图形生成的程序运行界面 2.创建工程名称为―基本图形的生成‖单文档应用程序框架 (1)启动VC,选择―文件‖|―新建‖菜单命令,并在弹出的新建对话框中单击―工程‖标签。 (2)选择MFC AppWizard(exe),在―工程名称‖编辑框中输入 ―基本图形的生成‖作为工程名称,单击―确定‖按钮,出现Step 1对话框。 (3)选择―单个文档‖选项,单击―下一个‖按钮,出现Step 2对话框。 (4)接受默认选项,单击―下一个‖按钮,在出现的Step 3~Step 5对话框中,接受默认选项,单击―下一个‖按钮。 (5)在Step 6对话框中单击―完成‖按钮,即完成―基本图形的生成‖应用程序的所有选项,随后出现工程信息对话框(记录以上步骤各选项选择情况),如图1-5所示,单击―确定‖按钮,完成应用程序框架的创建。 图1-5 信息程序基本 3.编辑菜单资源 设计如图1-4所示的菜单项。在工作区的ResourceView标签中,单击Menu项左边―+‖,然后双击其子项IDR_MAINFRAME,并根据表1-1中的定义编辑菜单资源。此时VC已自动建好程序框架,如图1-5所示。 表1-1 菜单资源表 菜单标题 菜单项标题 标示符ID 直线 DDA算法生成直线 ID_DDALINE Bresenham算法生成直线 ID_BRESENHAMLINE 中点算法生成直线 ID_MIDPOINTLINE 4.添加消息处理函数 利用ClassWizard(建立类向导)为应用程序添加与菜单项相关的消息处理函数,ClassName栏中选择CMyView,根据表1-2建立如下的消息映射函数,ClassWizard会自动完成有关的函数声明。 表1-2 菜单项的消息处理函数 菜单项ID 消 息 消息处理函数 ID_DDALINE CONMMAN OnDdaline ID_MIDPOINTLINE CONMMAN OnMidpointline ID_BRESENHAMLINE CONMMAN OnBresenhamline 5.程序结构代码,在CMyView.cpp文件中相应位置添加如下代码: // DDA算法生成直线 void CMyView:: OnDdaline() { CDC* pDC=GetDC();//获得设备指针 int xa=100,ya=300,xb=300,yb=200,c=RGB(255,0,0);//定义直线的两端点,直线颜色 int x,y; float dx, dy, k; dx=(float)(xb-xa), dy=(float)(yb-ya); k=dy/dx, y=ya; if(abs(k)<1) { for (x=xa;x<=xb;x++) {pDC->SetPixel (x,int(y+0.5),c); y=y+k;} } if(abs(k)>=1) { for (y=ya;y<=yb;y++) {pDC->SetPixel (int(x+0.5),y,c); x=x+1/k;} } ReleaseDC(pDC); } 说明: (1)以上代码理论上通过定义直线的两端点,可得到任意端点之间的一直线,但由于一般屏幕坐标采用右手系坐标,屏幕上只有正的x, y值,屏幕坐标与窗口坐标之间转换知识请参考第3章。 1的情形x每增加1,y最多增加1;当k?(2)注意上述程序考虑到当k>1时,y每增加1,x相应增加1/k。在这个算法中,y与k用浮点数表示,而且每一步都要对y进行四舍五入后取整。 //中点算法生成直线 void CMyView::OnMidpointline() { CDC* pDC=GetDC(); int xa=300, ya=200, xb=450, yb=300,c=RGB(0,255,0); float a, b, d1, d2, d, x, y; a=ya-yb, b=xb-xa, d=2*a+b; d1=2*a, d2=2* (a+b); x=xa, y=ya; pDC->SetPixel(x, y, c); while (x { if (d<0) {x++, y++, d+=d2; } else {x++, d+=d1;} pDC->SetPixel(x, y, c); } ReleaseDC(pDC); } 说明: (1)其中d是xp, yp的线性函数。为了提高运算效率,程序中采用增量计算。具体算法如下:若当前像素处于d>0情况,则取正右方像素P1(xp+1, yp),判断下一个像素点的位置,应计算d1=F(xp+2, yp+0.5)=a(xp+2)+b(yp+0.5)=d+a;,其中增量为a。若d<0时,则取右上方像素P2(xp+1, yp+1)。再判断下一像素,则要计算d2 = F(xp+2, yp+1.5)=a(xp+2)+b(yp+1.5) + c=d+a+b,增量为a+b。 (2) 画线从(x0, y0)开始,d的初值d0=F(x0+1, y0+0.5)=F(x0, y0)+a+0.5b,因F(x0, y0)=0,则d0=a+0.5b。 (3)程序中只利用d的符号,d的增量都是整数,只是初始值包含小数,用2d代替d,使程序中仅包含整数的运算。 //Bresenham算法生成直线 void CMyView::OnBresenhamline() { CDC* pDC=GetDC(); int x1=100, y1=200, x2=350, y2=100,c=RGB(0,0,255); int i,s1,s2,interchange; float x,y,deltax,deltay,f,temp; x=x1; y=y1; deltax=abs(x2-x1); deltay=abs(y2-y1); if(x2-x1>=0) s1=1; else s1=-1; if(y2-y1>=0) s2=1; else s2=-1; if(deltay>deltax){ temp=deltax; deltax=deltay; deltay=temp; interchange=1; } else interchange=0; f=2*deltay-deltax; pDC->SetPixel(x,y,c); for(i=1;i<=deltax;i++){ if(f>=0){ if(interchange==1) x+=s1; else y+=s2; pDC->SetPixel(x,y,c); f=f-2*deltax; } else{ if(interchange==1) y+=s2; else x+=s1; f=f+2*deltay; } } } 说明: (1)以上程序已经考虑到所有象限直线的生成。 (2)Bresenham算法的优点如下: ① 不必计算直线的斜率,因此不做除法。 ② 不用浮点数,只用整数。 ③ 只做整数加减运算和乘2运算,而乘2运算可以用移位操作实现。 ④ Bresenham算法的运算速度很快。 1.2 圆 给出圆心坐标(xc, yc)和半径r,逐点画出一个圆周的公式有下列两种。 1.2.1 直角坐标法 直角坐标系的圆的方程为 由上式导出: 当x–xc从–r到r做加1递增时,就可以求出对应的圆周点的y坐标。但是这样求出的圆周上的点是不均匀的,| x–xc | 越大,对应生成圆周点之间的圆周距离也就越长。因此,所生成的圆不美观。 1.2.2 中点画圆法 如图1-6所示,函数为F(x, y)=x2+y2–R2的构造圆,圆上的点为F(x, y)=0,圆外的点F(x, y)>0,圆内的点F(x, y)<0,构造判别式: d=F(M)=F(xp+1, yp–0.5)=(xp+1)2+(yp–0.5)2 若d<0,则应取P1为下一像素,而且下一像素的判别式为 d=F(xp+2, yp–0.5)= (xp+2)2+(yp–0.5)2–R2=d+2xp+3 若d≥0,则应取P2为下一像素,而且下一像素的判别式为 d=F(xp+2, yp–1.5)= (xp+2)2+(yp–1.5)2–R2=d+2(xp– yp)+5 我们讨论按顺时针方向生成第二个八分圆,则第一个像素是(0, R),判别式d的初始值为 d0=F(1, R–0.5)=1.25–R 1.2.3 圆的Bresenham算法 设圆的半径为r,先考虑圆心在(0, 0),从x=0、y=r开始的顺时针方向的1/8圆周的生成过程。在这种情况下,x每步增加1,从x=0开始,到x=y结束,即有xi+1 = xi + 1;相应的,yi+1则在两种可能中选择:yi+1=yi或者yi+1 = yi – 1。选择的原则是考察精确值y是靠近yi还是靠近yi–1(见图1-7),计算式为 y2= r2–(xi+1)2 d1 = yi2–y2 = yi2–r2+(xi+1)2 d2 = y2– (yi – 1)2 = r2–(xi+1)2–(yi – 1)2 令pi=d1–d2,并代入d1、d2,则有 pi = 2(xi+1)2 + yi2+ (yi – 1)2 –2r2 (1.6) pi称为误差。如果pi<0,则yi+1=yi,否则yi+1=yi – 1。 pi的递归式为 pi+1 = pi + 4xi +6+2(yi2+1– yi2)–2(yi+1–yi) (1.7) pi的初值由式(1.6)代入xi=0,yi=r,得 p1 = 3–2r (1.8) 根据上面的推导,圆周生成算法思想如下: (1)求误差初值,p1=3–2r,i=1,画点(0, r)。 (2)求下一个光栅位置,其中xi+1=xi+1,如果pi<0则yi+1=yi,否则yi+1=yi – 1。 (3)画点(xi+1, yi+1)。 (4)计算下一个误差,如果pi<0则pi+1=pi+4xi+6,否则pi+1=pi+4(xi – yi)+10。 (5)i=i+1,如果x=y则结束,否则返回步骤(2)。 程序设计步骤如下。 (1)创建应用程序框架,以上面建立的单文档程序框架为基础。 (2)编辑菜单资源。 在工作区的ResourceView标签中,单击Menu项左边―+‖,然后双击其子项IDR_MAINFRAME,并根据表1-3中的定义添加编辑菜单资源。此时建好的菜单如图1-8所示。 表1-3 菜单资源表 菜单标题 菜单项标题 标示符ID 圆 中点画圆 ID_MIDPOINTCIRCLE Bresenham画圆 ID_BRESENHAMCIRCLE (3)添加消息处理函数。 利用ClassWizard(建立类向导)为应用程序添加与菜单项相关的消息处理函数,ClassName栏中选择CMyView,根据表1-4建立如下的消息映射函数,ClassWizard会自动完成有关的函数声明。 表1-4 菜单项的消息处理函数 菜单项ID 消 息 消息处理函数 ID_MIDPOINTCIRCLE CONMMAN OnMidpointcircle ID_BRESENHAMCIRCLE CONMMAN OnBresenhamcircle (4)程序结构代码,在CMyView.cpp文件中的相应位置添加如下代码。 void CMyView::OnMidpointcircle() //中点算法绘制圆,如图1-9所示 { // TODO: Add your command handler code here CDC* pDC=GetDC(); int xc=300, yc=300, r=50, c=0; int x,y; float d; x=0; y=r; d=1.25-r; pDC->SetPixel ((xc+x),(yc+y),c); pDC->SetPixel ((xc-x),(yc+y),c); pDC->SetPixel ((xc+x),(yc-y),c); pDC->SetPixel ((xc-x),(yc-y),c); pDC->SetPixel ((xc+y),(yc+x),c); pDC->SetPixel ((xc-y),(yc+x),c); pDC->SetPixel ((xc+y),(yc-x),c); pDC->SetPixel ((xc-y),(yc-x),c); while(x<=y) { if(d<0) d+=2*x+3; else { d+=2*(x-y)+5; y--;} x++; pDC->SetPixel ((xc+x),(yc+y),c); pDC->SetPixel ((xc-x),(yc+y),c); pDC->SetPixel ((xc+x),(yc-y),c); pDC->SetPixel ((xc-x),(yc-y),c); pDC->SetPixel ((xc+y),(yc+x),c); pDC->SetPixel ((xc-y),(yc+x),c); pDC->SetPixel ((xc+y),(yc-x),c); pDC->SetPixel ((xc-y),(yc-x),c); } } void CMyView::OnBresenhamcircle() //// Bresenham算法绘制圆,如图1-10所示 { CDC* pDC=GetDC(); int xc=100, yc=100, radius=50, c=0; int x=0,y=radius,p=3-2*radius; while(x pDC->SetPixel(xc+x, yc+y, c); pDC->SetPixel(xc-x, yc+y, c); pDC->SetPixel(xc+x, yc-y, c); pDC->SetPixel(xc-x, yc-y, c); pDC->SetPixel(xc+y, yc+x, c); pDC->SetPixel(xc-y, yc+x, c); pDC->SetPixel(xc+y, yc-x, c); pDC->SetPixel(xc-y, yc-x, c); if (p<0) p=p+4*x+6; else { p=p+4*(x-y)+10; y-=1; } x+=1; } if(x==y) pDC->SetPixel(xc+x, yc+y, c); pDC->SetPixel(xc-x, yc+y, c); pDC->SetPixel(xc+x, yc-y, c); pDC->SetPixel(xc-x, yc-y, c); pDC->SetPixel(xc+y, yc+x, c); pDC->SetPixel(xc-y, yc+x, c); pDC->SetPixel(xc+y, yc-x, c); pDC->SetPixel(xc-y, yc-x, c); } 1.3 椭圆扫描转换中点算法 下面讨论椭圆的扫描转换中点算法,设椭圆为中心在坐标原点的标准椭圆,其方 程为 F(x, y)=b2x2+a2y2–a2b2=0 (1)对于椭圆上的点,有F(x, y)=0; (2)对于椭圆外的点,F(x, y)>0; (3)对于椭圆内的点,F(x, y)<0。 以弧上斜率为–1的点作为分界将第一象限椭圆弧分为上下两部分(如图1-11所示)。 法向量: 而在下一个点,不等号改变方向,则说明椭圆弧从上部分转入下部分。 与中点绘制圆算法类似,一个像素确定后,在下面两个候选像素点的中点计算一个判别式的值,再根据判别式符号确定离椭圆最近的点。先看椭圆弧的上半部分,具体算法如下: 假设横坐标为xp的像素中与椭圆最近点为(xp, yp),下一对候选像素的中点应为(xp+1,yp–0.5),判别式为 ,表明中点在椭圆内,应取正右方像素点,判别式变为 若 ,表明中点在椭圆外,应取右下方像素点,判别式变为 判别式 的初始条件确定。椭圆弧起点为(0, b),第一个中点为(1,b – 0.5),对应判别式为 在扫描转换椭圆的上半部分时,在每步迭代中需要比较法向量的两个分量来确定核实从上部分转到下半部分。在下半部分算法有些不同,要从正上方和右下方两个像素中选择下一个像素。在从上半部分转到下半部分时,还需要对下半部分的中点判别式进行初始化。即若上半部分所选择的最后一个像素点为(xp, yp),则下半部分中点判别式应在(xp+0.5, yp–1)的点上计算。其在正下方与右下方的增量计算同上半部分。具体算法的实现请参考下面的程序设计。 程序设计步骤如下。 (1)创建应用程序框架,以上面建立的单文档程序框架为基础。 (2)编辑菜单资源。 在工作区的ResourceView标签中,单击Menu项左边―+‖,然后双击其子项IDR_MAINFRAME,并根据表1-5中的定义添加编辑菜单资源。此时建好的菜单如图1-12所示。 表1-5 菜单资源表 菜单标题 菜单项标题 标示符ID 椭圆 中点画椭圆 ID_MIDPOINTELLISPE 图1-12 程序主菜单 (3)添加消息处理函数。 利用ClassWizard(建立类向导)为应用程序添加与菜单项相关的消息处理函数,ClassName栏中选择CMyView,根据表1-6建立如下的消息映射函数,ClassWizard会自动完成有关的函数声明。 表1-6 菜单项的消息处理函数 x)/(spt[i+1].y-spt[i].y); if(spt[i].y<=spt[i+1].y){ edge[i].num=i; edge[i].ymin=spt[i].y; edge[i].ymax=spt[i+1].y; edge[i].xmin=(float)spt[i].x; edge[i].xmax=(float)spt[i+1].x; pmax=spt[i+1].y; pmin=spt[i].y; } else{ edge[i].num=i; edge[i].ymin=spt[i+1].y; edge[i].ymax=spt[i].y; edge[i].xmax=(float)spt[i].x; edge[i].xmin=(float)spt[i+1].x; pmax=spt[i].y; pmin=spt[i+1].y; } } for(int r=1;r<6;r++) //排序edge(yUpper,xIntersect) { for(int q=0;q<6-r;q++) { if(edge[q].ymin newedge[0]=edge[q]; edge[q]=edge[q+1]; edge[q+1]=newedge[0]; } } } for(int scan=pmax-1;scan>pmin+1;scan--) { int b=0; k=s; for(j=k;j<6;j++) { if((scan>edge[j].ymin)&&(scan<=edge[j].ymax))//判断与线段相交 { if(scan==edge[j].ymax) { if(spt[edge[j].num+1].y p[b]=(int)edge[j].xmax; } if(spt[edge[j].num-1].y p[b]=(int)edge[j].xmax; } } if((scan>edge[j].ymin)&&(scan p[b]=(int)(edge[j].xmax+edge[j].dx*(scan-edge[j]. ymax)); } } //pDC->LineTo(spt[edge[0].num].x,spt[edge[0].num].y); if(scan<=edge[j].ymin)// s=j; } if(b>1) { for(int u=1;u pDC->MoveTo(p[u]+3,scan); u++; pDC->LineTo(p[u],scan); } } } pDC->SelectObject(old); } 说明:双击,出现需填充的多边形,单击相关功能菜单实现区域填充。 void CMyView::OnSeedfill() //种子算法进行多边形区域填充,如图1-20所示 { CWindowDC dc (this); int fill=RGB(0,255,0); int boundary=RGB(255,0,0); CPoint pt=s_point; int x,y,p0,pmin,pmax; //求多边形的最大最小值 for(int m=1;m<7;m++) { for(int n=0;n<7-m;n++) { if(spt[n].y p0=spt[n].y; spt[n]=spt[n+1]; spt[n+1]=p0; } } } pmax=spt[0].y,pmin=spt[6].y; x=s_point.x; y=s_point.y; for(;y int current=dc.GetPixel(x,y); while((current!=boundary)&&(current!=fill)) { dc.SetPixel(x,y,fill); x++; current=dc.GetPixel(x,y); } x=s_point.x; x--; current=dc.GetPixel(x,y); while((current!=boundary)&&(current!=fill)) { dc.SetPixel(x,y,fill); x--; current=dc.GetPixel(x,y); } x=s_point.x; } x=s_point.x; y=s_point.y-1; for(;y>pmin-2;y--) { int current=dc.GetPixel(x,y); while((current!=boundary)&&(current!=fill)) { dc.SetPixel(x,y,fill); x++; current=dc.GetPixel(x,y); } x=s_point.x; x--; current=dc.GetPixel(x,y); while((current!=boundary)&&(current!=fill)) { dc.SetPixel(x,y,fill); x--; current=dc.GetPixel(x,y); } x=s_point.x; } } 说明: (1)双击后,出现需填充的多边形,单击相关功能菜单实现区域填充。 (2)进行种子填充,需右击多边形内一点,作为开始填充的种子点。 void CMyView::OnLButtonDblClk(UINT nFlags, CPoint point) { RedrawWindow(); CDC* pDC=GetDC(); CPen newpen(PS_SOLID,1,RGB(255,0,0)); CPen *old=pDC->SelectObject(&newpen); spt[0]=CPoint(100,100); //绘制多边形区域 spt[1]=CPoint(300,100); spt[2]=CPoint(250,250); spt[3]=CPoint(100,250); spt[4]=CPoint(150,200); spt[5]=CPoint(90,180); spt[6]=CPoint(150,150); spt[7]=CPoint(100,100); pDC->Polyline(spt,8); pDC->SelectObject(old); ReleaseDC(pDC); CView::OnLButtonDblClk(nFlags, point); } void CMyView::OnRButtonDown(UINT nFlags, CPoint point) { s_point=point; //选择种子点 CView::OnRButtonDown(nFlags, point); } 1.5 字符的生成 字符指数字、字母、汉字等符号。计算机中字符由一个数字编码惟一标识。国际上最流行的字符集是《美国信息交换用标准代码集》,简称ASCII码。它是用7位二进制数进行编码表示128个字符,包括字母、标点、运算符以及一些特殊符号。我国除采用ASCII码外,还另外制定了汉字编码的国家标准字符集GB 2312-1980《信息交换用汉字编码字符集 基本集》。该字符集分为94个区,94个位,每个符号由一个区码和一个位码共同标识。区码和位码各用一个字节表示。为了能够区分ASCII码与汉字编码,采用字节的最高位来标识:最高位为0表示ASCII码;最高位为1表示汉字编码。为了在显示器等输出设备上输出字符,系统中必须装备有相应的字库。字库中存储了每个字符的形状信息,字库分为矢量和点阵型两种形式,如图1-21所示。 (a)点阵字符 (b)点阵字库中的位图表示 (c)矢量轮廓字符 图1-21 字符的种类 1.5.1 点阵字符 在点阵字符库中,每个字符由一个位图表示。该位为1表示字符的笔画经过此位,对应于此位的像素应置为字符颜色。该位为0表示字符的笔画不经过此位,对应于 此位的像 素应置为背景颜色。在实际应用中,有多种字体(如宋体、楷体等),每种字体又有多种大小型号,因此字库的存储空间是很庞大的。解决这个问题一般采用压 缩技术。如黑白段压缩、部件压缩、轮廓字形压缩等。其中,轮廓字形法压缩比大, 且能保证字符质量,是当今国际上最流行的一种方法。轮廓字形法采用直线或二/三 次bezier曲线的集合来描述一个字符的轮廓线。轮廓线构成一个或若干个封闭的平面区域。轮廓线定义加上一些指示横宽、竖宽、基点、基线等控制信息就构成了字符的压缩数据。 点阵字符的显示分为两步。首先从字库中将它的位图检索出来。然后将检索到的位图写到帧缓冲器中。 1.5.2 矢量字符 矢量字符记录字符的笔画信息而不是整个位图,具有存储空间小,美观、变换方便等优点。对于字符的旋转、缩放等变换,点阵字符的变换需要对表示字符位图中的每一像素进行;而矢量字符的变换只要对其笔画端点进行变换就可以了。矢量字符的显示也分为两步。首先从字库中找到它的字符信息。然后取出端点坐标,对其进行适当的几何变换,再根据各端点的标志显示出字符。 1.5.3 字符属性 字符属性一般包括字体、字高、字宽因子(扩展/压缩)、字倾斜角、对齐方式、字色和写方式等。字符属性的内容如下。 (1)字体:如仿宋体、楷体、黑体、隶书; (2)字倾斜角:如倾斜; (3)对齐:如左对齐、中心对齐、右对齐; (4)字色:如红、绿、蓝色; (5)写方式:替换方式时,对应字符掩模中空白区被置成背景色。写方式时,这部分区域颜色不受影响。 1.6 图 形 裁 剪 在使用计算机处理图形信息时,计算机内部存储的图形往往比较大,而屏幕显示的只是图的一部分。因此需要确定图形中哪些部分落在显示区之内,哪些落在显示区之外,以便只显示落在显示区内的那部分图形。这个选择过程称为裁剪。最简单的裁剪方法是把各种图形扫描转换为点之后,再判断各点是否在窗内。但那样太费时,一般不可取。这是因为有些图形组成部分全部在窗口外,可以完全排除,不必进行扫描转换。所以一般采用先裁剪再扫描转换的方法,多边形裁剪示意图,如图1-22所示。 (a)裁剪前 (b)裁剪后 图1-22 多边形裁剪示意图 1.6.1 线裁剪 1.直线和窗口的关系 直线和窗口的关系如图1-23所示,可以分为如下3类: (1)整条直线在窗口内。此时,不需剪裁,显示整条直线。 (2)整条直线在窗口外,此时,不需剪裁,不显示整条直线。 (3)部分直线在窗口内,部分直线在窗口外。此时,需要求出直线与窗框的交点,并将窗口外的直线部分剪裁掉,显示窗口内的直线部分。 直线剪裁算法有两个主要步骤。首先将不需剪裁的直线挑出,即删去在窗外的直线。然后,对其余直线,逐条与窗框求交点,并将窗口外的部分删去。 2.Cohen-Sutherland直线剪裁算法 以区域编码为基础,将窗口及其周围的8个方向以4 bit的二进制数进行编码。如图1-24所示的编码方法将窗口及其邻域分为5个区域。 (1)内域:区域(0000)。 (2)上域:区域(1001,1000,1010)。 (3)下域:区域(0101, 0100, 0110)。 (4)左域:区域(1001, 0001, 0101)。 图1-24 窗口及其邻域的5个区域及与直线的关系 (5)右域:区域(1010, 0010, 0110)。 当线段的两个端点的编码的逻辑―与‖非零时,线段显然为不可见的。对某线段的两各端点的区号进行位与运算,可知这两个端点是否同在视区的上、下、左、右。算法的主要思想是,对每条直线,如P1P2利用以下步骤进行判断: ① 对直线两端点P1、P2编码分别记为C1(P1)={a1, b1, c1, d1},C2(P2)={a2, b2, c2, d2}其中,ai、bi、ci、di取值范围为 {1, 0},i∈{1, 2}。 ② 如果ai=bi=ci=di=0,则显示整条直线,取出下一条直线,返回步骤①;否则,进入步骤③。 ③ 如果| a1–a2 |=1,则求直线与窗上边(y=yw–max)的交点,并删去交点以上部分。如果| b1–b2 |=1,| c1–c2 |=1,| d1–d2 |=1,进行类似处理。 ④ 返回步骤①判断下一条直线。 1.6.2 多边形裁剪 多边形裁剪算法的关键在于,通过剪裁,要保持窗口内多边形的边界部分,而且要将窗框的有关部分按一定次序插入多边形的保留边界之间,从而使剪裁后的多边形的边仍然保持封闭状态,以便填色算法得以正确实现,多边形裁剪原理示意图,如 图1-25所示。 (a)剪裁的多边形 (b)按直线剪裁的多边形 (c)按多边形剪裁后的多边形 图1-25 多边形裁剪原理示意图1 (1)Sutherland-Hodgman算法思路: 将多边形的各边先相对于窗口的某一条边界进行裁剪,然后将裁剪结果再与另一条边界进行裁剪,如此重复多次,便可得到最终结果。 (2)实现方法: ① 设置两个表。输入顶点表(向量)——用于存放被裁剪多边形的顶点p1–pm。输出顶点表(线性链表)——用于存放裁剪过程中间结果的顶点q1–qn。 ② 输入顶点表中各顶点要求按一定顺序排列,一般可采用顺时针或逆时针方向。 ③ 相对于裁剪窗口的各条边界,按顶点表中的顺序,逐边进行裁剪。 (3)具体操作步骤如下: ① Pi若位于边界线的可见一侧,则Pi送给输出顶点表。 ② Pi若位于边界线的不可见一侧,则将其舍弃。 ③ 除第一个顶点外,还要检查每一个Pi和前一顶点Pi–1是否位于窗口边界的同一 侧,若不在同一侧,则需计算出交点送给输出顶点表。 ④ 最后一个顶点Pn则还要与P1一起进行同样的检查。 如图1-26所示,是上述多边形裁剪的原理示意图。 图1-26 多边形裁剪原理示意图2 1.6.3 字符裁剪 前面已经介绍了字符和文本的输出。当字符和文本部分在窗口内、部分在窗口外时,就提出了字符裁剪问题。字符串裁剪可按三个精度来进行:串精度、字符精度以及笔画/像素精度。采用串精度进行裁剪时,用包围字符串的外接矩形对窗口进行裁剪。当字符串方框整个在窗口内时予以显示,否则不显示。采用字符精度进行裁剪时,用包围字符的外接矩形对窗口作裁剪,某个字符方框整个落在窗口内予以显示,否则不显示。采用笔画/像素精度进行裁剪时,将笔画分解成直线段对窗口进行裁剪,处理方法同上。字符裁剪的原理示意图,如图1-27所示。 (a)待裁剪字符串 (b)串精度裁剪 (c)字符精度裁剪 (d)笔画/像素精度裁剪 图1-27 字符裁剪原理示意图 1.6.4 图形裁剪编程 1.程序设计功能说明 如图1-28所示为图形裁剪的实用程序运行时的主界面,首先根据界面提示,在 用户区双击,出现所需要裁剪的各种线段,再单击菜单中―图形裁剪‖,可选择其下拉菜单的各图形裁剪选项完成各种图形裁剪(在窗口中红矩形框外的线段或多边形被裁 减掉)。 图1-28 ―图形裁剪‖程序主界面 2.程序设计步骤 程序―图形裁剪‖的设计步骤如下: (1)创建工程名称为―图形裁剪‖单文档应用程序框架(参看上面单文档应用程序框架的建立)。 (2)编辑菜单资源。 设计如图1-29所示的菜单项。在工作区的ResourceView标签中,单击Menu项左边―+‖,然后双击其子项IDR_MAINFRAME,并根据表1-9中的定义编辑菜单资源。 表1-9 菜单资源表 菜单标题 菜单项标题 标示符ID 图形裁剪 线段裁剪 ID_CLIPLINE 多边形裁剪 ID_CLIPPOLYGON (3)添加消息处理函数。 利用ClassWizard(建立类向导)为应用程序添加与菜单项相关的消息处理函数,ClassName栏中选择CMyView,根据表1-10建立如下的消息映射函数,ClassWizard会自动完成有关的函数声明。 表1-10 菜单项的消息处理函数 菜单项ID 消 息 消息处理函数 ID_CLIPLINE CONMMAN OnIDTRANSLATION ID_CLIPPOLYGON CONMMAN OnIDROTATION (4)添加代码,在图形裁剪应用程序的相应文件中添加如下黑体字部分代码。 ① 在―图形裁剪View.h‖文档中的适当位置添加定义存储线段端点的数组。 class CMyView : public CView { protected: // create from serialization only ? public: CPoint ptset[N]; ? }; ② 在―图形裁剪View.cpp‖文档中的适当位置手工添加以下黑体部分代码。 #include \ #include \图形裁剪.h\? #endif #define LEFT 1 #define RIGHT 2 #define BOTTOM 4 #define TOP 8 #define XL 300 #define XR 500 #define YT 100 #define YB 200 ////////////////////////////////////////////////////////////////// void CMyView::OnDraw(CDC* pDC)//功能为程序开始呈现下面的界面 { CMyDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); // TODO: add draw code for native data here CPen newpen(PS_SOLID,1,RGB(255,0,0)); CPen *old=pDC->SelectObject(&newpen); pDC->Rectangle(CRect(XL,YT,XR,YB)); //剪切窗口,可通过修改上面的对应数据修改裁剪矩形框 ptset[0]=CPoint(320,150); //需要剪切的各种线段,可通过修改数据修改线段(见图1-30) ptset[1]=CPoint(370,110); ptset[2]=CPoint(200,190); ptset[3]=CPoint(550,150); ptset[4]=CPoint(200,250); ptset[5]=CPoint(350,230); ptset[6]=CPoint(400,50); ptset[7]=CPoint(450,130); pDC->TextOut(0,0,\双击, 出现要剪切的线段\ pDC->TextOut(0,0,\双击鼠标右键, 出现要剪切的多边形\pDC->SelectObject(old); } ////////////////////////////////////////////////////////////////// //处理双击左键消息函数,得到要进行裁剪的直线段 void CMyView::OnLButtonDblClk(UINT nFlags, CPoint point) { CDC* pDC=GetDC(); CPen newpen(PS_SOLID,1,RGB(255,0,0)); CPen *old=pDC->SelectObject(&newpen); for(int i=0;i pDC->MoveTo(ptset[i]); pDC->LineTo(ptset[i+1]); i++; } CView::OnLButtonDblClk(nFlags, point); } void CMyView::OnClipline() //线段裁剪消息处理函数 { CDC* pDC=GetDC(); CPen newpen(PS_SOLID,1,RGB(0,255,0)); CPen *old=pDC->SelectObject(&newpen); if(flag!=1) {MessageBox(\请先双击\警告!\(如图1-31所示) else { float x,y,x1,x2,y1,y2; int i; int code1,code2; RedrawWindow(); // 求两端点所在区号code for(i=0;i if(ptset[i].x if(ptset[i+1].x //线段与区域的相交情况(见图1-32) if(code1!=0&&code2!=0&&(code1&code2)==0) { if((LEFT&code1)!=0) //线段与左边界相交 { x=XL; y=ptset[i].y+(ptset[i+1].y-ptset[i].y)*(XL-ptset[i].x)/(ptset [i+1].x-ptset[i].x); } else if((RIGHT&code1)!=0) //线段与右边界相交 { x=XR; y=ptset[i].y+(ptset[i+1].y-ptset[i].y)*(XR-ptset[i].x)/(ptset [i+1].x-ptset[i].x); } else if((BOTTOM&code1)!=0) //线段与下边界相交 { y=YB; x=ptset[i].x+(ptset[i+1].x-ptset[i].x)*(YB-ptset[i].y)/(ptset [i+1].y-ptset[i+1].y); } else if((TOP&code1)!=0) //线段与上边界相交 { y=YT; x=ptset[i].x+(ptset[i+1].x-ptset[i].x)*(YT-ptset[i].y)/(ptset [i+1].y-ptset[i].y); } ptset[i].x=x; ptset[i].y=y; if((LEFT&code2)!=0) //线段与左边界相交 { x=XL; y=ptset[i].y+(ptset[i+1].y-ptset[i].y)*(XL-ptset[i].x)/(ptset [i+1].x-ptset[i].x); } else if((RIGHT&code2)!=0) //线段与右边界相交 { x=XR; y=ptset[i].y+(ptset[i+1].y-ptset[i].y)*(XR-ptset[i].x)/(ptset [i+1].x-ptset[i].x); } else if((BOTTOM&code2)!=0) //线段与下边界相交 { y=YB; x=ptset[i].x+(ptset[i+1].x-ptset[i].x)*(YB-ptset[i].y)/(ptset [i+1].y-ptset[i+1].y); } else if((TOP&code2)!=0) //线段与上边界相交 { y=YT; x=ptset[i].x+(ptset[i+1].x-ptset[i].x)*(YT-ptset[i].y)/(ptset [I+1].y-ptset[i].y); } ptset[i+1].x=x; ptset[i+1].y=y; pDC->MoveTo(ptset[i].x,ptset[i].y); pDC->LineTo(ptset[i+1].x,ptset[i+1].y); } if(code1==0&&code2==0) {pDC->MoveTo(ptset[i].x,ptset[i].y); pDC->LineTo(ptset[i+1].x,ptset[i+1].y); } if(code1==0&&code2!=0) { pDC->MoveTo(ptset[0].x,ptset[0].y); if((LEFT&code2)!=0) //线段与左边界相交 { x=XL; y=ptset[i].y+(ptset[i+1].y-ptset[i].y)*(XL-ptset[i].x)/(ptset [i+1].x-ptset[i].x); } else if((RIGHT&code2)!=0) //线段与右边界相交 { x=XR; y=ptset[i].y+(ptset[i+1].y-ptset[i].y)*(XR-ptset[i].x)/(ptset [i+1].x-ptset[i].x); } else if((BOTTOM&code2)!=0) //线段与下边界相交 { y=YB; x=ptset[i].x+(ptset[i+1].x-ptset[i].x)*(YB-ptset[i].y)/(ptset [i+1].y-ptset[i+1].y); } else if((TOP&code2)!=0) //线段与上边界相交 { y=YT; x=ptset[i].x+(ptset[i+1].x-ptset[i].x)*(YT-ptset[i].y)/(ptset [i+1].y-ptset[i].y); } ptset[i+1].x=x; ptset[i+1].y=y; pDC->LineTo(ptset[i+1].x,ptset[i+1].y); } if(code1!=0&&code2==0) { pDC->MoveTo(ptset[i+1].x,ptset[i+1].y); if((LEFT&code1)!=0) //线段与左边界相交 { x=XL; y=ptset[i].y+(ptset[i+1].y-ptset[i].y)*(XL-ptset[i].x)/(ptset [i+1].x-ptset[i].x); } else if((RIGHT&code1)!=0) //线段与右边界相交 { x=XR; y=ptset[i].y+(ptset[i+1].y-ptset[i].y)*(XR-ptset[i].x)/(ptset [i+1].x-ptset[i].x); } else if((BOTTOM&code1)!=0) //线段与下边界相交 { y=YB; x=ptset[i].x+(ptset[i+1].x-ptset[i].x)*(YB-ptset[i].y)/(ptset [i+1].y-ptset[i+1].y); } else if((TOP&code1)!=0) //线段与上边界相交 { y=YT; x=ptset[i].x+(ptset[i+1].x-ptset[i].x)*(YT-ptset[i].y)/(ptset [i+1].y-ptset[i].y); } ptset[i].x=x; ptset[i].y=y; pDC->LineTo(ptset[i].x,ptset[i].y); } } } } //处理双击右键出现要裁剪的多边形(见图1-33) void CMyView::OnRButtonDblClk(UINT nFlags, CPoint point) { CDC* pDC=GetDC(); CPen newpen(PS_SOLID,1,RGB(255,0,0)); CPen *old=pDC->SelectObject(&newpen); pDC->MoveTo(ptset1[0]); for(int i=1;i<5;i++) { pDC->LineTo(ptset1[i]); } CView::OnRButtonDblClk(nFlags, point); } void CMyView::OnClippolygon() 多边形裁剪(见图1-34) { CDC* pDC=GetDC(); CPen newpen(PS_SOLID,1,RGB(0,255,0)); CPen *old=pDC->SelectObject(&newpen); if(flag!=1) {MessageBox(\请先双击鼠标右键\警告!\(见图1-35) else { int i,k; int code1,code2; int M=5; RedrawWindow(); // 求两端点所在区号code k=0; for(i=0;i if(ptset1[i].x if(ptset1[i+1].x if(code1!=0&&code2==0) { pt[k].x=XL; pt[k].y=ptset1[i].y+(ptset1[i+1].y-ptset1[i].y)*(XL- ptset1[i].x)/(ptset1[i+1].x-ptset1[i].x); pt[k+1].x=ptset1[i+1].x; pt[k+1].y=ptset1[i+1].y; k=k+2; } if(code1==0&&code2==0) { if(k==0) { pt[k].x=ptset1[i].x; pt[k].y=ptset1[i].y; pt[k+1].x=ptset1[i+1].x; pt[k+1].y=ptset1[i+1].y; k=k+2; } if(k!=0) { pt[k].x=ptset1[i+1].x; pt[k].y=ptset1[i+1].y; k=k+1; } } if(code1==0&&code2!=0) { pt[k].x=XL; pt[k].y=ptset1[i].y+(ptset1[i+1].y-ptset1[i].y)*(XL- ptset1[i].x)/(ptset1[i+1].x-ptset1[i].x); k++; } } pt[k].x=pt[0].x; pt[k].y=pt[0].y; M=k+1; k=0; for(i=0;i if(pt[i].x if(pt[i+1].x if(code1==0&&code2==0) { if(k==0) { pts[k].x=pt[i].x; pts[k].y=pt[i].y; pts[k+1].x=pt[i+1].x; pts[k+1].y=pt[i+1].y; k=k+2; } if(k!=0) { pts[k].x=pt[i+1].x; pts[k].y=pt[i+1].y; k++; } } if(code1!=0&&code2==0) { pts[k].x=XR; pts[k].y=pt[i].y+(pt[i+1].y-pt[i].y)*(XR-pt[i].x)/(pt[i+ 1].x-pt[i].x); pts[k+1].x=pt[i+1].x; pts[k+1].y=pt[i+1].y; k=k+2; } if(code1==0&&code2!=0) { pts[k].x=XR; pts[k].y=pt[i].y+(pt[i+1].y-pt[i].y)*(XR-pt[i].x)/(pt[i+ 1].x-pt[i].x); k=k+1; } } //处理最后一条边 pts[k]=pts[0]; M=k+1; k=0; for(i=0;i if(pts[i].y>YB) c=4; else if(pts[i].y if(pts[i+1].y>YB) c=4; else if(pts[i+1].y if(code1==0&&code2==0) { if(k==0) { ptse[k].x=pts[i].x; ptse[k].y=pts[i].y; ptse[k+1].x=pts[i+1].x; ptse[k+1].y=pts[i+1].y; k=k+2; } if(k!=0) { ptse[k].x=pts[i+1].x; ptse[k].y=pts[i+1].y; k=k+1; } } if(code1!=0&&code2==0) { ptse[k].y=YB; ptse[k].x=pts[i].x+(pts[i+1].x-pts[i].x)*(YB-pts[i].y)/ (pts[i+1].y-pts[i+1].y); ptse[k+1].x=pts[i+1].x; ptse[k+1].y=pts[i+1].y; k=k+2; } } if(code1==0&&code2!=0) { ptse[k].y=YB; ptse[k].x=pts[i].x+(pts[i+1].x-pts[i].x)*(YB-pts[i].y)/ (pts[i+1].y-pts[i+1].y); k=k+1; } ptse[k]=ptse[0]; M=k+1; k=0; for(i=0;i if(ptse[i].y>YT) c=0; else if(ptse[i].y if(ptse[i+1].y>YT) c=0; else if(ptse[i+1].y if(code1!=0&&code2==0) { p[k].y=YT; p[k].x=ptse[i].x+(ptse[i+1].x-ptse[i].x)*(YT-ptse[i].y)/ (ptse[i+1].y-ptse[i].y); p[k+1].x=ptse[i+1].x; p[k+1].y=ptse[i+1].y; k=k+2; } if(code1==0&&code2==0) { if(k==0) { p[k].x=ptse[i].x; p[k].y=ptse[i].y; p[k+1].x=ptse[i+1].x; p[k+1].y=ptse[i+1].y; k=k+2; } if(k!=0) { p[k].x=ptse[i+1].x; p[k].y=ptse[i+1].y; k=k+1; } } if(code1==0&&code2!=0) { p[k].y=YT; p[k].x=ptse[i].x+(ptse[i+1].x-ptse[i].x)*(YT-ptse[i].y)/ (ptse[i+1].y-ptse[i].y); k++; } } p[k]=p[0]; M=k+1; pDC->MoveTo(p[0]); for(int j=1;j<=M;j++) { pDC->LineTo(p[j]); } } } 说明:为避免上述―图形裁剪‖程序执行过程中,在没有双击就直接单击―线段裁剪‖或 没有双击就直接单击―多边形裁剪‖的情况发生,设置了消息对话框进行 警告。 1.7 Visual C++中基本绘图函数 实际利用Visual C++中编制图形程序,可以利用上述算法自己动手编制基本图形程序,作为图形程序的基类,当然还可利用系统中已提供的图形基类。下面简单介绍Visual C++提供的常用绘制图形函数。 1.点 画点是最基本的绘图操作,在绘图中,画点是通过调用CDC::SetPixel()或CDC∷SetPixelV()函数来实现的,原型如: (1)COLORREF SetPixel(int x, int y, COLORREF crColor); (2)COLORREF SetPixel(POINT point, COLORREF crColor); (3)BOOL SetPixelV(int x, int y, COLORREF crColor); (4)BOOL SetPixelV(POINT point, COLORREF crColor); 2.画笔 一般格式:Cpen( )∷Cpen(int nPenStyle, int nWidth, CORLORREF crColor); 各属性意义:nPenStyle设置画笔的式样,式样有:PS_SOLID(实线),PS_DASH(虚线)、PS_DASHDOT(点划线)、PS_DASHDOTDOT(双点划线)、PS_DOT(点线)、PS_NULL(空笔不画线);nWidth设置线的宽度,默认值为1(1个像素宽);crColor表示颜色,可用DWOR表示,也可用RGB(r, g, b)表示。 3.画刷 用于指定填充的特征,画刷创建的格式如下: (1)CBrush∷CBrush(创建一个空的画刷对象),可用GreateSolidBrush(),Greateha- tchBrush(),GreatehatchBrushIndrect(),GreatePatternBrush(),GreateDIBPatternBrush()建立画刷。 (2)CBrush∷CBrush()建立单一颜色的画刷,用次画刷画出的图形内部将会填充指定颜色。 (3)CBrush∷CBrush(int nIndex, COLORREF crColor);构建名为hatch的画刷,特点为画出的多边形内部将填充nrColor指定的线条格式,nrColor有HS_BDIAGONAL(45°左下→右上的斜线)、HS_CROSS(垂直线和水平线)、HS_DIAGCROSS(45°左上→右下、左上→右下的相交斜线)、HS_HDLAGNAL(45°左上→右下的斜线)、HS_HORIZONAL(水平线)、HS_VERTICAL(垂直线)。 (4)CBrush∷CBrush(Cbitmap *pBitmap)中pBitmap指向Cbitmap对象的指针,这一位图对象包含用做画刷图案的位图,此位图必须为8×8大小,否则将对原位图进行裁剪。 创建画刷和画笔后,还要用CDC类选中画笔和画刷,用CPaintDC,CClientDC或CWindowDC来选中、绘图及撤销对象。 CClientDC对象代表客户程序区域的绘图画面只能在窗口的客户区域中画图。若需处理整个画面(包括客户程序区域和非客户程序区)设备上下文的调用和释放可用CWindowDC。 4.绘制直线函数 (1)MoveTo()函数用来设置当前的x,y的位置,创建的格式如下: CPoint MoveTo(int x, int y); CPoint MoveTo(POINT point); 其中x,y用于定义新位置的坐标,point指定新位置,可为其传递一个POINT对象。 功能:将线的起点从当前位置移到新位置(x,y),并且只移动点不画线。 (2)LineTo()用于绘制起点坐标到终点直线,创建的格式如下: BOOL LineTo(int x, int y); BOOL LineTo(POINT point); 其中x,y用于定义线的终点坐标,point指定线段端点位置,可为其传递一个POINT结构或POINT对象。 功能:从当前的位置到新位置(x,y)画线(不包括此端点)。 5.椭圆函数 创建的格式如下: BOOL Ellipse(int x1, int y1 , int x2, int y2); BOOL Ellipse(LPCRECT lpRect); 说明:x1, y1为限定椭圆范围的矩形左上角坐标,x2, y2为限定椭圆范围的矩形右下角坐标。 LpRect指定椭圆的限定矩形,可为其传递一个CRect对象。 6.函数绘制一段椭圆弧Arc() 创建的格式如下: BOOL Arc(int x1, int y1 , int x2, int y2, int x3, int y3 , int x4, int y4); BOOL Ellipse(LPCRECT lpRect); x1, y1为限定椭圆弧范围的矩形左上角坐标;x2, y2为限定椭圆弧范围的矩形右下角坐标。x3, y3为起点坐标;x4, y4为终点坐标。 7.矩形函数 创建的格式如下: BOOL Rectangle(int x1, int y1, int x2, int y2); x1, y1为矩形左上角坐标,x2, y2为矩形右下角坐标。 功能:使用当前画笔画一矩形。 8.连续画线函数 创建的格式如下: (1)BOOL PolyLine(LPPOINT lpPoints , int nCount); 说明:lpPoints指向POINT结构数组,数组中每一个结构标识一个点的坐标。 nCount:为定义数组中的点数,使用当前画笔从第一个点开始经后续点连续画线直到最后一个点。 (2)BOOL PolyLineTo(LPPOINT *lpPoints , int nCount); 说明:lpPoints指向POINT结构数组指针,画一条或多条直线的指针,数组中存放直线顶点的坐标。 nCount:为定义数组中的点数。 (3)BOOL PolyBezier(LPPOINT *lpPoints); 说明:lpPoints指向POINT结构数组指针,画一条或多条直线的指针,数组中包括曲线的重点和控制点。 nCount:为定义数组中的点数。 绘制三次贝塞尔曲线需要两个控制点及一个终点及一个起点,共4个点决定一条贝塞尔曲线。 (4)BOOL PolyBezierTo(LPPOINT *lpPoints); 画一条或多条贝塞尔曲线。 lpPoints指向POINT结构数组指针,画一条或多条直线的指针,数组中包括曲线的重点和控制点。 9.多边形绘制函数 创建的格式如下: (1)BOOL Polygon(counst POINT lpPoints, int nCount); 说明:lpPoint指定多边形顶点数组中每一点是一个POINT结构或一个CPoint对 象,nCount指定数组中顶点数。 (2)BOOL PolyPolygon(LPPOINT lpPoints,lpint lpPolyCounts, int lpPoints); 说明:lpPoint指向一个POINT结构或CPoint对象数组,每个数组定义一个多边形的顶点;lpPolyCounts指向一个整数数组,每个整数说明lpPoints数组中一个多边形的顶点数,nCount:为LpPolyCount数组中的项数,即指定要画的多边形数,最多为2。 10.填充函数 创建的格式如下: (1)BOOL FillSolidRect(LPCRECT lpRect, COLORREF crColor); (2)BOOL FillSolidRect(int x, int y ,int cx, int cy, COLORREF Clr); 说明:LpRect指定矩形可传递一个指向RECT结构的指针或CRect对象。 Clr为填充颜色;x,y为矩形左下角坐标,cx为矩形宽,cy为矩形高。 (3)BOOL ExtFloodFill(int x, int y, COLORREF crColor,UINT nFillType); 说明:x,y为开始填充处坐标;crColor为填充颜色;FloodFillBorder指填充区域由crColor参数所指定颜色包围部分;FloodFillSurface表示填充区域是由nColor指定颜色来定义矩形填充。 (4)BOOL FloodFill(int x, int y ,COLORREF Clr); 说明:x, y 为填充处逻辑坐标或边界颜色,crColor指定填充颜色。 以上简单介绍了系统中提供的基本绘图函数,更多内容请参见其他参考书。 练 习 题 1.为什么说直线生成算法是二维图形生成技术的基础? 2.根据DDA算法编制绘制从(10, 10)到(300,400)直线的程序。 3.将中点画线算法推广以便能画出任意斜率的直线。 4.使用中点分割算法实现对直线段进行裁剪的程序。 5.利用本章所建程序框架,在菜单项―圆‖的子菜单中添加―直角坐标画圆‖子菜单,并添加相应命令和代码实现直角坐标画圆程序,加深对画圆算法的理解,并与其他算法程序相比较。 6.利用Visual C++已定义函数实现上述直线、圆、椭圆等图形的绘制,并进行区域填充。 满怀希望的 2009-09-24 22:52:19 函数功能:该函数将指定坐标处的像素设为指定的颜色。 函数原型:COLORREF SetPixel(HDC hdc, int X, int Y, COLORREF crColor); 参数: hdc:设备环境句柄。 X:指定要设置的点的X轴坐标,按逻辑单位表示坐标。 Y:指定要设置的点的Y轴坐标,按逻辑单位表示坐标。 crColor:指定要用来绘制该点的颜色。 返回值:如果函数执行成功,那么返回值就是函数设置像素的RGB颜色值。这个值可能与crColor指定的颜我色有不同,之所以有时发生这种情况是因为没有找到对指定颜色进行真正匹配造成的;如果函数失败,那么返回值是C1。 Windows NT:若想获得更多的错误信息,请调用GetLastError函数。 备注:如果像素点坐标位于当前剪辑区之外,那么该函数执行失败。 不是所有设备都支持SetPixel函数。有关详情,请参考GetDeviceCaps。 速查:Windows NT:3.1及以上版本;Windows:95及以上版本;Windows CE:1.0及以上版本;头文件:wingdi.h:库文件:gdi32.lib。 其它相关 Graphics.setPixel Sets a pixel to the specified color. Syntax public final void setPixel( Point pt ) public final void setPixel( Point pt, Color color ) public final void setPixel( Point pt, Color color, RasterOp op ) public final void setPixel( int x, int y ) public final void setPixel( int x, int y, Color color ) public final void setPixel( int x, int y, Color color, RasterOp op )
正在阅读:
图形05-30
20XX年机关公务员党员思想汇报范文:加强反腐倡廉工作10-13
高鸿业宏观经济学习题+答案01-21
运动会管理系统05-07
维修电工必会习题01-09
2018届高三数学(理)高考总复习升级增分训练立体几何Word版含解析12-07
党性个人自我剖析及整改措施合集08-22
实用英语1课后习题答案(unit 1-unit 5)07-10
重要昆虫分科9个11-11
伦敦金融城合作方案09-11
- 多层物业服务方案
- (审判实务)习惯法与少数民族地区民间纠纷解决问题(孙 潋)
- 人教版新课标六年级下册语文全册教案
- 词语打卡
- photoshop实习报告
- 钢结构设计原理综合测试2
- 2014年期末练习题
- 高中数学中的逆向思维解题方法探讨
- 名师原创 全国通用2014-2015学年高二寒假作业 政治(一)Word版
- 北航《建筑结构检测鉴定与加固》在线作业三
- XX县卫生监督所工程建设项目可行性研究报告
- 小学四年级观察作文经典评语
- 浅谈110KV变电站电气一次设计-程泉焱(1)
- 安全员考试题库
- 国家电网公司变电运维管理规定(试行)
- 义务教育课程标准稿征求意见提纲
- 教学秘书面试技巧
- 钢结构工程施工组织设计
- 水利工程概论论文
- 09届九年级数学第四次模拟试卷
- 担保公司对被担保企业审计方法的探讨
- 关于施工三处春节前存在资金严重困难的紧急报告
- 高职英语第二册期末考试试题卷
- 简析城市轨道交通运营管理模式
- 江南大学2018年上半年小学生心理健康第1阶段练习题参考_ss
- 人教版小学语文四年级下册全册教案
- 2017广东省中考名著精选文段与答案
- 五年级数学下册口算题
- 实验4 数据完整性
- 井控技术管理复习题及答案
- 初中数学北师大版八年级下册第六单元第2-1课《平行四边形的判定
- 病例对照研究(卫管卫法)
- 尼龙护套线
- 大学生饮食消费权益维护意识分析 - —以山西师范大学为例
- 商业战略合作协议书 模板
- 2015 - 2016学年度西师大版第二学期三年级语文期中试卷
- 【2018最新】森林防火调研报告-word范文模板 (3页)
- 员工职业化的6堂必修课111
- 浙大远程2010秋《老年护理学》第一次必做作业答案
- 人教版小学三年级数学上册时分秒综合练习题精选58