GDI编程
一、GDI相关概念
1、GDI(Graphics Device Interface):图形设备接口,是一个应用程序与输出设备之间的中介。它提供了一套函数库,这些函数在不同的输出设备上输出图形和文字。一方面,GDI向应用程序提供一个与设备无关的编程环境,另一方面,它又以设备相关的格式和具体的设备打交道。
2、DC (Device Context):设备描述表(设备上下文),是一种Windows数据结构,包括了如线的宽度和颜色、刷子的样式和颜色、字体、剪裁区域等信息。用于表达显示器、打印机等设备。
DC的主要作用是进行绘图和输出文字,如绘制线条、形状和文本等,具体如dc.MoveTo(),dc.LineTo(),dc.Ellipse(),dc.FillRect(),dc.FillSolidRect(),dc.TextOut()等。
Win32下与HDC相关的函数有:GetDC(), BeginPaint()/EndPaint(),GetWindowDC()等
对应的MFC版本的类有:CDC, CPaintDC, CClientDC, CWindowDC等
3、GDI对象:DC定义了一组GDI对象,包括画笔,画刷,字体,位图,调色板,剪裁区域,路径层(Path)。他们有Win32和MFC两套实现版本,其对应关系如下:
Win32对象 |
MFC类 |
HPEN |
CPen |
HBRUSH |
CBrush |
HFONT |
CFont |
HBITMAP |
CBitmap |
HPALETTE |
CPalette |
HRGN |
CRgn |
4、DC与GDI对象之间的关系:GDI对象是通过DC发生作用的,要使用这些GDI对象,可以使用Win32函数SelectObject来将其选入DC中,如::SelectObject(hdc, hPen);
5、利用DC和GDI对象绘图的完整步骤为:
(1). 获取或者创建一个DC
(2). 获取或者创建一个GDI对象(Pen, Brush等)
(3). 使用dc.SelectObject函数把GDI对象选入DC
(4). 使用DC进行绘图或文字输出
(5). 恢复DC原来的GDI对象并删除刚新创建的GDI对象,如pen.DeleteObject()
(6). 释放或删除设备描述表DC
其中,(1)和(6),(2)和(4)是成对出现的。
二、设备描述表DC
Win32下获取DC的API函数有:
HDC BeginPaint(HWND hwnd, LPPAINTSTRUCT lpPaint):特定用于WM_PAINT消息
HDC GetDC(HWND hWnd):用于获得hWnd参数所指定窗口的客户区域的HDC。
HDC GetWindowDC(HWND hWnd):返回hWnd参数所指定的窗口的HDC,包括非客户区,如标题栏、菜单、滚动条,以及边框等。hWnd为NULL时,获取整个屏幕的HDC。
MFC对上述HDC对象和Win32函数进行了封装,基类为CDC类。CDC类包含了各种Win32 HDC的全部功能。在MFC下,使用CDC的成员函数进行图形绘制和文字输出。
CDC类有两个成员变量:m_hDC,m_hAttribDC,它们都是Windows设备描述表句柄。CDC的成员函数作输出操作时,使用m_Hdc;要获取设备描述表的属性时,使用m_hAttribDC。在创建一个CDC类实例时,缺省的m_hDC等于m_hAttribDC。
CDC在封装Win32函数SelectObject(HDC hdc,HGDIOBJECT hgdiobject)时,采用了重载技术,即它针对不同的GDI对象,提供了如下名同而参数不同的成员函数:
SelectObject(CPen *pen) //用于选入笔
SelectObject(CBitmap* pBitmap) //用于选入位图
SelectObject(CRgn *pRgn) //用于选入剪裁区域
SelectObject(CBrush *pBrush) //用于选入刷子
SelectObject(CFont *pFont) //用于选入字体
SelectPalette(CPalette *pPalette,BOOL bForceBackground ) //选入调色板到DC
RealizePalletter() //实现逻辑调色板到物理调色板的映射
直接使用CDC的例子是内存设备上下文,例如:
CDC dcMem.CreateCompatibleDC(&dc); //创建设备描述表
CDC pbmOld = dcMem.SelectObject(&m_bmBall); //更改设备描述表属性
//作一些绘制操作
dcMem.SelectObject(pbmOld); //恢复设备描述表的属性
dcMem.DeleteDC(); //可以不调用,而让析构函数去删除设备描述表
从CDC 派生出四个功能更具体的DC类。继承层次如下图所示:
下面分别讨论这四种设备描述表。
l CCientDC:代表窗口客户区的设备描述表。其构造函数CClientDC(CWnd *pWin)通过::GetDC获取指定窗口的客户区的设备描述表HDC,并且使用成员函数Attach把它和CClientDC对象捆绑在一起;其析构函数使用成员函数Detach把设备描述表句柄HDC分离出来,并调用::ReleaseDC释放设备描述表HDC。
l CPaintDC:仅仅用于响应WM_PAINT消息时绘制窗口,因为它的构造函数调用了::BeginPaint获取设备描述表HDC,并且使用成员函数Attach把它和CPaintDC对象捆绑在一起;析构函数使用成员函数Detach把设备描述表句柄HDC分离出来,并调用::EndPaint释放设备描述表HDC,而::BeginPaint和::EndPaint仅仅在响应WM_PAINT时使用。
例如,MFC中CView对WM_PAINT消息的实现方法如下:
void CView::OnPaint()
{
CPaintDC dc(this);
OnPrepareDC(&dc);
OnDraw(&dc);
}
l CMetaFileDC:用于生成元文件。
l CWindowDC:代表整个窗口区(包括非客户区)的设备描述表。其构造函数CWindowDC(CWnd *pWin)通过::GetWindowDC获取指定窗口的客户区的设备描述表HDC,并使用Attach把它和CWindowDC对象捆绑在一起;其析构函数使用Detach把设备描述表HDC分离出来,调用::ReleaseDC释放设备描述表HDC。
三、使用DC进行绘图的基本过程
l 获取或者创建设备描述表.DC;
l 必要的话,改变设备描述表的属性(见第四节:GDI对象的介绍);
l 使用设备描述表完成绘制操作;
l 释放或删除设备描述表DC。
第一种绘图方式是对WM_PAINT消息的处理
void CAaView::OnPaint()
{
CPaintDC dc(this); // 得到绘图DC
dc.TextOut(100,100,"Hello World");
}
或者
void CAaView::OnDraw(CDC *pDC)
{
pDC->TextOut(100,100,"Hello World");
}
上面的程序可以在窗口的100,100位置处,打印Hello World字符串。
那么什么时候会产生WM_PAINT消息呢?由于Windows是一个多任务环境,某个应用程序的窗口上面可能被对话框或窗口覆盖,当撤消这些对话框或窗口时,这个应用程序窗口中就有一个"空洞",这个"空洞"就是一块无效的用户区域。为重新显示无效用户区域,Windows发送WM_PAINT消息实现。要求Windows发送WM_PAINT的情况有:改变窗口大小,覆盖用户区的菜单或对话框关闭,使用UpdateWindow和ScrollWindow函数等。
Windows发送WM_PAINT消息时,把它放到应用程序队列的最后,使得其它的输入能够先于WM_PAINT消息被处理。GetMessage函数也得到队列中WM_PAINT消息之后的其它消息,即只有有没有其它消息的情况下,才从队列中取出WM_PAINT消息进行处理。这样做是为了让应用程序首先完成影响窗口显示结果的其它操作,不致因为频繁地执行输出操作而引起显示器的闪烁。Windows把WM_PAINT消息放在队列最后就是这个原因。
Windows并非WM_PAINT消息的唯一来源,使用InvalidateRect或InvalidateRgn函数也可以产生绘图窗口的WM_PAINT消息。这两个函数把用户区全部或部分标记成无效用户区而要求重新显示。下面的函数调用是把整个用户区标记成无效:
InvalidateRect(hWnd, NULL, TRUE);
上面代码把hWnd句柄参数指定的窗口用户区标记成无效。作为矩形结构的NULL参数指定整个用户区,TRUE参数表示擦除背景。
第二种绘图的方式是在非OnDraw / OnPaint中绘图
void CAaView::OnLButtonDown(UINT nFlags, CPoint point)
{
CClientDC dc(this);
dc.Ellipse(point.x-50, point.y-50, point.x+50, point.y+50);
}
这段程序实现了:以鼠标的当前位置为圆心,画一个半径为50的圆。
基本的画线函数有以下几种
CDC::MoveTo( int x, int y ); 改变当前点的位置
CDC::LineTo( int x, int y ); 画一条由当前点到参数指定点的线
CDC::BOOL Arc( LPCRECT lpRect, POINT ptStart, POINT ptEnd ); 画弧线
CDC::BOOL Polyline( LPPOINT lpPoints, int nCount ); 将多条线依次序连接
基本的作图函数有以下几种:
CDC::BOOL Rectangle( LPCRECT lpRect ); 矩形
CDC::RoundRect( LPCRECT lpRect, POINT point ); 圆角矩形
CDC::Draw3dRect( int x, int y, int cx, int cy,
COLORREF clrTopLeft, COLORREF clrBottomRight ); 3D边框
CDC::Chord( LPCRECT lpRect, POINT ptStart, POINT ptEnd ); 扇形
CDC::Ellipse( LPCRECT lpRect ); 椭圆形
CDC::Pie( LPCRECT lpRect, POINT ptStart, POINT ptEnd );
CDC::Polygon( LPPOINT lpPoints, int nCount ); 多边形
对于矩形,圆形或类似的封闭曲线,系统会使用画笔绘制边缘,使用刷子填充内部。如果你不希望填充或是画出边缘,你可以选入空(NULL_PEN)笔或空(NULL_BRUSH)刷子。
多边形和剪贴区域
dc.CreateRectRgn 由矩形创建一个多边形
dc.CreateEllipticRgn 由椭圆创建一个多边形
dc.CreatePolygonRgn 创建一个有多个点围成的多边形
dc.PtInRegion 某点是否在内部
dc.CombineRgn 两个多边形相并
dc.EqualRgn 两个多边形是否相等
基本的绘图函数
CDC类中提供各种各样的输出操作,从画线到写字应有尽有。为了画线、矩形、圆、扇形和写字,可相应地调用一些函数。这些函数使用已选择的画笔和画刷,来画出边框,并填写图形内部区域,以及使用已选择的字体写字。
l 画点函数SetPixel
COLORREF CDC::SetPixel(int x,int y,COLORREF cclrref);
该函数把x和y指定的点置为clrref指定的颜色。
l 画线函数LineTo与移动函数MoveTo
LineTo函数用来画线,并且通常与MoveTo函数配合使用,如画一条从点(10,70)到点(250,100)的线:
dc.MoveTo(10,70);
dc.LineTo(250,100);
l 画矩形函数Rectangle
Rectangle函数用来画矩形。它使用已选择的画笔画出边框,使用已选择的刷子填满矩形内部。下面的例子画一个左上角位于点(10,20),右下角位于点(40,100)的矩形:
dc.Rectangle(10,20,40,100);
l 画圆或椭圆函数Ellipse
Ellipse函数用来画圆或椭圆。它使用已选择的笔画框,使用已选择的刷填满圆或椭圆的内部。下面的例子画一个用点(10,20)和点(40,100)构成矩形框中的椭圆:
dc.Ellipse(10,20,40,100);
l 画圆弧函数Arc
Arc函数用来画一段弧,这段弧由包围它的矩形和弧的开始点和结束点共同定义。下面的例子在点(10,90)和点(360,120)所指定的矩形中画一段弧,它的起点和终点分别是点(15,90)和点(360,90):
dc.Arc(10,90,360,120,15,90,360,110);
弧的起点坐标和终点坐标精确地位于弧上。
l 画扇形函数Pie
Pie函数用来画扇形。扇形由一段弧和两条从弧焦点到弧端点的半径组成。Pie函数使用已选择的笔画框,使已选择的刷填满扇形内部。下面的例子画一个用点(310,30)和点(360,80)构成的矩形围成的扇形。其起点和终点分别为点(360,30)和点(360,80):
dc.Pie(310,30,360,80,310,30,360,80);
弧的起点和终点不必精确地位于弧线上。
四、GDI对象
前面的程序只能画基本的图形,我们不能改变线条的颜色,线条的大小,不能填充颜色,也不能改变字体,显示一张位图等。要实现这些功能,我们就要使用GDI对象。不过,GDI对象是要通过DC才能发生作用的。要使用这些GDI对象,必须使用SelectObject函数将其选入DC中,如::SelectObject(hdc, hPen);
当然,使用之前,这些GDI对象必须存在,可以通过如下Win32函数来创建这些对象:
或者,通过Win32函数HGDIOBJ GetStockObject(int fnObject)来获取系统预先定义好的如下备用对象:
fnObject参数 含义
这里需再次强调一下:GDI对象要选入Windows 设备描述表后才能使用;用毕,要恢复设备描述表的原GDI对象,并删除该GDI对象。
一般按如下步骤使用GDI对象:
a、创建或得到一个GDI对象
b、使用dc.SelectObject函数把GDI对象选入DC
c、使用DC进行绘图或文字输出
d、恢复DC原来的GDI对象并删除刚新创建的GDI对象。
综合DC和GDI对象的使用步骤,则绘图的完整步骤为:
1. 获取或者创建一个DC
2. 获取或者创建一个GDI对象(Pen, Brush等)
3. 使用dc.SelectObject函数把GDI对象选入DC
4. 使用DC进行绘图或文字输出
5. 恢复DC原来的GDI对象并删除刚新创建的GDI对象,如pen.DeleteObject()
6. 释放或删除设备描述表DC
其中,1和6,2和4是对应的。
MFC GDI对象
MFC用一些类封装了Windows GDI对象和相关函数,层次结构如图所示:
(1)画笔
画笔决定了线条的颜色、宽度和线型(实线、点线或点划线等)。Windows使用当前在设备描述表中已选择的画笔来画线。程序中可以选择Windows的予定义画笔,也可以选择自定义的画笔。
预定义画笔有三种:BLACK_PEN(黑色笔) 、WHITE_PEN(白色笔)和NULL_PEN(空笔),这些都在windows.h中已经定义好了,程序员可使用GetStockObject函数来选择其中的一种,系统缺省的画笔为黑色笔。Windows.h包含了HPEN的数据类型定义,使用该类型可以定义画笔句柄的变量。
仅靠系统提供的预定义画笔远远不能满足需求,应用程序可根据实际需要创建一种自定义的逻辑画笔。其步骤一般为:首先用CreatePen或CreatePenIndirect函数建立一支画笔,再调用SelectObject函数将其选入设备描述表,此后就可使用该画笔在选定的设备描述表中进行绘图操作。任何时候某一设备描述表只能有一支画笔被选入作为当前画笔,当一支画笔被选入时,原先已选入的画笔便不再有效。完成绘图操作后,可以通过调用DeleteObject来释放已建立的画笔。
* 函数CreatePen()
语法:HPEN CreatePen(int fnPenStyle,int nWidth,COLORREF clrref);
说明:该函数创建一个逻辑画笔。其中
fnPenStyle参数指定画笔的线型,该参数可取由windows.h定义的七个标识符之一,其含义为:
PS_SOLID 实线
PS_DASH 虚线
PS_DOT 点线
PS_DASHDOT 夹一点虚线
PS_DASHDOTDOT 夹二点虚线
PS_NULL 无
PS_INSIDEFRAME 线画在所有构件框架内
nWidth参数是用逻辑单位表示的画笔的宽度;
clrref参数是一个COLORREF类型的颜色值,指定画笔的颜色,可用宏指令RGB构造这个值,如:clrref=RGB(byRed,byGreen,byBlue);
在使用CreatPen函数时,要检查其返回值,确保它是一个有效的句柄。
下面给出一段程序,说明建立、选择和释放画笔的一般方法,假定程序要用一支宽度为3的黑色作图,则程序如下:
CPen *p_Pen;
CDC dc;
p_Pen->CreatePen(PS_SOLD,3,RGB(0,0,0));
if(p_Pen)
{
dc.SelectObject(p_Pen);
//… //这里进行绘图操作
}
Delete p_Pen; //删除hPen画笔,释放空间
* 函数CreatePenIndirect()
语法:HPEN CreatePenIndirect(LOGPEN FAR* lpLogPen);
说明:该函数用lpLogPen所指的LOGPEN结构中的信息创建一个逻辑画笔。
LOGPEN的结构如下:
typedef struct tagLOGPEN(
WORD lopnStyle;
POINT lopnWidth;
COLORREF lopnColor;
) LOGPEN;
其中lopnStyle指定画笔线型,该参数可取下列值之一:
PS_SOLID 0
PS_DASH 1
PS_DOT 2
PS_DASHDOT 3
PS_DASHDOTDOT 4
PS_NULL 5
PS_INSIDEFRAME 6
nWidth参数是用逻辑单位表示的画笔的宽度
clrref参数是一个COLORREF类型的颜色值,指定画笔的颜色,可用宏指令RGB构造这个值。
(2)刷子
当我们在绘制一些区域图形时,其内部往往需要以某种图案进行填充,这就需要选定"刷子"作为绘图工具。Windows系统不仅为用户提供了预定义刷子,而且还允许应用程序自定义刷子。
Windows系统中预定义的刷子有如下七种:
BLACK_BRUSH 黑色刷子
DKGRAY_BRUSH 深灰色刷子
GRAY_BRUSH 灰色刷子
HOLLOW_BRUSH 中空刷子,画边界而不填充
LTGRAY_BRUSH 浅灰色刷子
NULL_BRUSH 空刷子
WHITE_BRUSH 白色刷子
应用程序可以调用GetStockObject函数选用其中一个,系统缺省的刷子是白色刷子。Window.h包含了HBRUSH数据类型的定义,使用该类型就可定义刷子句柄的变量。
仅靠这七种刷子往往不能满足要求,应用程序通过调用如下几种函数创建逻辑刷子,这些函数返回值均为刷子句柄。
* 函数CreateHatchBrush()
语法:HBRUSH CreateHatchBrush(int fnStyle, COLORREF clrref);
说明: 该创建一个带阴影的逻辑刷子。
FnStyle指定的阴影格式如下:
HS_BDLAGONAL 45度向上斜线组成的阴影图案(自左到右)
HS_CROSS 水平和垂直交叉组成的阴影图案
HS_DIAGCROSS 45度斜线交叉组成的阴影图案
HS_FDIAGONAL 45度向下斜线组成的阴影图案(自左到右)
HS_HORZONA 水平线组成的阴影图案
HS_VERTICAL 垂直线组成的阴影图案
Clrref是具有COLORREF类型定义的刷子颜色值,可用宏指令RGB构造这个值。
* 函数CreateSolidBrush()
语法:HBRUSH CreateSolidBrush(COLORREF clrre);
说明:该函数创建的是一种实心颜色的逻辑刷子。clrre含义同上。
同样,使用创建刷子的函数时,要检查其返回,确保它是一个有效的句柄。
一旦创建了绘图工具之后,可以SelectObject函数把它选择到显示缓冲区里。
在使用显示缓冲区之前,并不一定非要创建和选择绘图工具,Windows为每个显示缓冲区提供默认的绘图工具。例如:黑色笔,白色刷子和系统字体。
DeleteObject函数用来删除不再需要的绘图工具,但不能删除一个已选进显示缓冲区的绘图工具,而是应该使用SelectObject函数恢复原有的绘图工具,然后再删除需要删除的工具。
(3) 填 充 图 形
绘制一些需要以某种图案进行填充的区域图形时,需要选定"刷子"作为绘图工具。Windows系统中预定义的刷子有七种,应用程序可以调用GetStockObject函数选用其中一个,系统缺省的刷子是白色刷子。
当靠这七种刷子不能满足要求时,应用程序通过调用Windows函数创建逻辑刷子,这些函数返回值均为刷子句柄。
(4) 文字与字体
Windows是使用定义好的与设备无关的字符集,Windows的"文本"字符也是图形,所以屏幕上所显示的用打印机或绘图仪等输出品的文本完全一样,做到"所见即所得"。
文本绘制函数有:
TextOut 以当前的字体写一字符串
DrawText 在一个特定矩形区中绘制某一格式的文本
ExtTextOut 在一个特定矩形区中,以当前字体写一字符串
GrayString 用灰色文本写一字符串
TabbedTextOut 写一带扩展字符的字符串
要输出文本就离不开字体。获取字体的相关信息可以使用函数:
BOOL GetTextMetrics( LPTEXTMETRIC lpMetrics )
结构TEXTMETRIC的定义如下所示:
typedef struct tagTEXTMETRIC { // tm
LONG tmHeight; //字符高度
LONG tmAscent; //字符上部高度(基线以上)
LONG tmDescent; //字符下部高度(基线以下)
LONG tmInternalLeading; //由tmHeight定义的字符高度的顶部空间数目
LONG tmExternalLeading; //加在两行之间的空间数目
LONG tmAveCharWidth; //平均字符宽度
LONG tmMaxCharWidth; //最宽字符的宽度
LONG tmWeight; //字体的粗细轻重程度
LONG tmOverhang; //加入某些拼接字体上的附加高度
LONG tmDigitizedAspectX; //字体设计所针对的设备水平方向
LONG tmDigitizedAspectY; //字体设计所针对的设备垂直方向
BCHAR tmFirstChar; //为字体定义的第一个字符
BCHAR tmLastChar; //为字体定义的最后一个字符
BCHAR tmDefaultChar; //字体中所没有字符的替代字符
BCHAR tmBreakChar; //用于拆字的字符
BYTE tmItalic; //字体为斜体时非零
BYTE tmUnderlined; //字体为下划线时非零
BYTE tmStruckOut; //字体被删去时非零
BYTE tmPitchAndFamily; //字体间距(低4位)和族(高4位)
BYTE tmCharSet; //字体的字符集
} TEXTMETRIC;
GDI字体族和字样
GDI字体族和字样表如下表所示:
字体族 字体族常量 字样说明
Dontcare FF_DONTCARE System 当不能提供字体信息或字体并不
重要时使用
Decorative FF_DECORATIVE Symbol 新奇字体
Modern FF_MODERN Courer,ModernIerminal 笔画大小固定的字体,但衬线可有可无
Roman FF_ROMAN Roman,TimeRoman 有衬线的、笔画大小可变的罗马字体
Script FF_SCRIPT Script 仿手写体
Swiss FF_SWISS Helvetical,System 无衬线的、笔画大小可变的字体
* CreateFontIndirect函数
语法: HFONT CreateFontIndirect(
CONST LOGFONT *lplf // pointer to logical font structure
);
说明:参数lplf是LOGFONT结构的指针。结构中含有逻辑字体的特征信息。该函数用lplf所指的LOGFONT结构中的信息创建一种逻辑字体。LOGFONT结构的定义如下:
typedef struct tagLOGFONT { // lf
LONG lfHeight; //字高度
LONG lfWidth; //字符平均宽度
LONG lfEscapement; //行与水平页角度
LONG lfOrientation; //基线与水平角度
LONG lfWeight; //笔划的粗细
BYTE lfItalic; //非零为斜体
BYTE lfUnderline; //非零为下划线
BYTE lfStrikeOut; //非零为中划线
BYTE lfCharSet; //指定字符集
BYTE lfOutPrecision; //输出精度
BYTE lfClipPrecision; //裁剪精度
BYTE lfQuality; //输出质量
BYTE lfPitchAndFamily; //字体的字距和族
TCHAR lfFaceName[LF_FACESIZE]; //含字体名的字符串
} LOGFONT;
* 函数SetTextAlign
大多数文本函数传递的参数表都要求有一个点坐标参数以定义写文本的参考点。当前文本对齐属性规定了字符串如何相对于所传递的坐标进行写。SetTextAlign函数用以设置当前文本对齐属性。
语法: UINT SetTextAlign(
HDC hdc, // handle to device context
UINT fMode // text-alignment flag
);
说明:该函数设置文本对齐方式。Hdc是参数描述表,fuAlign是文本对齐方式
* GetClientRect函数
语法: BOOL GetClientRect(
HWND hWnd, // handle to window
LPRECT lpRect // address of structure for client coordinates
);
说明: hWnd是与用户区域相关的窗口,lpRect是指向RECT结构的指针。
五、位图
位图是一个二维的位数组,它与图像的图素一一对应。当现实世界的图像被扫描成位图以后,图像被分割成网格,并以图素作为取样单位。在位图中的每个图素值指明了一个单位网格内图像的平均颜色。单色位图每个图素只需要一位,灰色或彩色位图中每个图素需要多个位。
画位图
BitBlt函数从称为「来源」的设备内容中将一个矩形区的图素传输到称为「目的(destination)」的另一个设备内容中相同大小的矩形区。
此函数的语法如下:
BitBlt (hdcDst, xDst, yDst, cx, cy, hdcSrc, xSrc, ySrc, dwROP) ;
xSrc和ySrc参数指明了来源图像左上角的坐标位置。
cx和cy参数是图像的宽度和高度。
xDst和yDst参数表示了复制图像位置左上角的坐标位置。
dwROP参数是位映像操作型态,可取以下值:
BLACKNESS 输出区域为黑色
DSTINVERT 反色输出区域
MERGECOPY 在源和目的间使用AND操作
MERGEPAINT 在反色后的目的和源间使用OR操作
NOTSRCCOPY 将反色后的源拷贝到目的区
PATINVERT 源和目的间进行XOR操作
SRCAND 源和目的间进行AND操作
SRCCOPY 复制源到目的区
SRCINVERT 源和目的间进行XOR操作
SRCPAINT 源和目的间进行OR操作
WHITENESS 输出区域为白色
拉伸位图
在BitBlt函数中,目的图像与来源图像的尺寸是相同的,因为函数只有两个参数来说明宽度和高度。如果您想在复制时拉伸或者压缩图像尺寸,可以使用StretchBlt函数。
StretchBlt函数的语法如下:
StretchBlt (hdcDst, xDst, yDst, cxDst, cyDst, hdcSrc, xSrc, ySrc, cxSrc, cySrc, dwROP) ;
此函数添加了两个参数。现在的函数就分别包含了目的和来源各自的宽度和高度。
六、文本编程实例
A. 光标插入符
CClientDC dc(this);
TEXTMETRIC tm; //简介TEXTMETRIC结构
dc.GetTextMetrics(&tm); //取得当前DC的相关文本信息,用于确定光标的宽和高
CreateSolidCaret(tm.tmAveCharWidth/8,tm.tmHeight); //创建光标插入符
ShowCaret();
SetCaretPos(point); //需要时可利用本函数移动光标
B. 位图插入符
CBitmap m_bitmap; //类成员变量
m_bitmap.LoadBitmap(IDB_BITMAP1);
CreateCaret(&m_bitmap);
ShowCaret();
SetCaretPos(point); //需要时可利用本函数移动光标
C. 使用指定字体输出字符串
CString sText = "毛主席语录:好好学习,天天向上";
CFont font;
font.CreatePointFont(200, "宋体", NULL);
CFont *pOldFont = pDC->SelectObject(&font); //选入新字体,保存老字体
pDC->TextOut(10, 80, sText); //在指定位置显示字符串
pDC->DrawText(sText, CRect(10, 120, 160, 200), DT_LEFT);
pDC->SelectObject(pOldFont); //恢复老字体
//比较TextOut与DrawText的差异:TextOut仅仅指定了字符串显示的起点位置,会将整个字符串全部显示出来。而DrawText限定了字符串显示的矩形框,只能显示在该矩形区域内,超出该区域的字符将不会显示出来。同时指定了对齐方式。
D. 设置字体背景透明,字体颜色(前景色)
pDC->SetTextColor(RGB(255, 0, 0)); //设置字体前景色,即字体颜色
pDC->SetBkColor(RGB(255, 255, 0)); //设置字体的背景色
pDC->SetBkMode(TRANSPARENT); //设置字体背景为透明。不要和透明刷子混淆了
pDC->TextOut(10, 80, sText); //完成各种设置后,开始显示字符串
详细解读:更加详细的综合示例代码见工程“TextDraw”。
七、图形编程实例
l 在Win32程序中画线
1. 定义两个全局变量用于记录鼠标按下的(x,y)坐标。
int nOrginX;
int nOrginY;
2. 响应鼠标按下和鼠标抬起的消息:
在Swich中加入case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
3. 在鼠标按下时记录鼠标按下的(x,y)坐标,查MSDN得知WM_LBUTTONDOWN
lParam的低字存放x坐标,高字存放y坐标,将其取出存入nOrginX,nOrginY。
case WM_LBUTTONDOWN:
nOrginX=lParam & 0x0000ffff;
nOrginY=lParam >> 16 & 0x0000ffff;
break;
4. 在鼠标抬起时画线:
case WM_LBUTTONUP:
HDC hdc;
hdc=GetDC(hwnd);
::MoveToEx(hdc,nOrginX,nOrginY,NULL);
::LineTo(hdc,LOWORD(lParam),HIWORD(lParam) );
::ReleaseDC(hwnd,hdc);
l 在MFC程序中画线:
1. 在CxxxView(其中xxx是你的工程名字)中响应鼠标按下和鼠标抬起的消息(因为只有CxxxView中才能接收到鼠标消息):
使用ClassWizard加入WM_LBUTTONDOWN,WM_LBUTTONUP的消息响应函数OnLButtonDown, OnLButtonUp。
2. 在CxxxView中添加成员变量CPoint m_ptOrigin,用于记录鼠标按下的(x,y)坐标。
CPoint是一个用于描述点的简单的类,它有两个成员变量可以存放点的(x,y)坐标。
3. 在鼠标按下时记录该点的坐标:
m_ptOrigin =point;其中point是调用OnLButtonDown传入的鼠标按下的点的坐标。
4. 在鼠标抬起时画线:
CClientDC dc(this);
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);
其中CClientDC 是一个CDC的子类,在它的构造函数中调用了GetDC,析构函数中调用了ReleaseDC,简化了用户的操作。
l 实现橡皮筋功能:
5. 再定义一个成员变量,用于记录鼠标抬起的点,以便擦线。
CPoint m_ptEnd;
6. 在鼠标按下时记录该点的坐标:
m_ptOrigin=m_ptEnd=point;
7.使用ClassWizard加入WM_MOUSEMOVE的消息响应函数OnMouseMove。
在鼠标移动时判断鼠标左鍵是否按下,如果按下,就不断地擦去上一条线,画出鼠标按下点到鼠标移动的当前点之间的线。
if(MK_LBUTTON & nFlags)
{
CClientDC dc(this);
dc.SetROP2(R2_NOT);
dc.MoveTo(m_ptOrigin);
dc.LineTo(m_ptEnd);
dc.MoveTo(m_ptOrigin);
dc.LineTo(point);
m_ptEnd=point;
}
其中:
if (MK_LBUTTON & nFlags)
是判断鼠标左鍵是否按下。在调用OnMouseMove时,不仅为用户传来了坐标信息,还把鼠标左鍵是否按下,Shift鍵是否按下(详细信息可查MSDN)等信息放在UINT nFlags中传入OnMouseMove,用户可以检查相应位是否为1来判断相应键是否按下。
dc.SetROP2(R2_NOT);
该句设置逆转当前屏幕颜色的绘图模式。这种模式下,在屏幕上首次画出的线的是可见的,但在同一位置再画一遍时,线就不见了。这样可以方便的实现不断画线、擦线的效果。
详细解读:更加详细的代码见工程“MiniCAD”。
作业:
1、完善时钟程序,使时针、分针和秒针的粗细不同,颜色不同。
2、完善MiniCAD工程,增加按下鼠标中键拖动画椭圆的功能。
3、阅读MiniNotepad工程,深入理解文本编程的具体应用。
选修内容1:如何改变客户区的背景色
方法一:
int CsdiView::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CView::OnCreate(lpCreateStruct) == -1)
return -1;
// TODO: 在此添加您专用的创建代码
HBRUSH hBrush = ::CreateSolidBrush(RGB(255, 0, 0));
::SetClassLong(m_hWnd, GCL_HBRBACKGROUND, (LONG)hBrush);
return 0;
}
方法二:
BOOL CsdiView::OnEraseBkgnd(CDC* pDC)
{
// TODO: 在此添加消息处理程序代码和/或调用默认值
CRect rc;
this->GetClientRect(&rc);
pDC->FillSolidRect(&rc, RGB(192, 255, 0));
return TRUE;
////或
//CRect rc;
//this->GetClientRect(&rc);
//CBrush brush(RGB(192, 255, 0));
//pDC->FillRect(&rc, &brush);
//return TRUE;
//return CView::OnEraseBkgnd(pDC);
}
方法三:
void CsdiView::OnDraw(CDC* pDC)
{
CsdiDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
if (!pDoc)
return;
// TODO: 在此处为本机数据添加绘制代码
CRect rc;
this->GetClientRect(&rc);
pDC->FillSolidRect(&rc, RGB(192, 255, 192));
}
方法四:
BOOL CsdiView::PreCreateWindow(CREATESTRUCT& cs)
{
// TODO: 在此处通过修改CREATESTRUCT cs 来修改窗口类或样式
if (!CView::PreCreateWindow(cs)) return FALSE;
cs.lpszClass = AfxRegisterWndClass(CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW,
::LoadCursor(NULL, IDC_ARROW), ::CreateSolidBrush(RGB(255, 0, 0)));//红色背景
return (cs.lpszClass != NULL);
}
选修内容2:利用双缓存避免屏幕闪烁
void CRussiaView::OnDraw(CDC* pDC)
{
CRussiaDoc * pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
/////////////////////////////////////////////////////////////////////////
//
//如下的做法能避免绘图闪烁, 强烈推荐使用. KouConghua.
//
//主要思想是将以前直接画在pDC上的图,改画到一个内存DC(如dcMem)中去,
//然后使用BitBlt函数,将dcMem这个内存中的图复制到当前屏幕即pDC中去.
//
//具体步骤如下, 其中a 和b 选择一步即可, 不可二者都做:
//a. 直接在OnDraw()中增加如下语句, 以改变窗口背景为透明色:
// ::SetClassLong(this->m_hWnd, GCL_HBRBACKGROUND,
// (LONG)(HBRUSH)::GetStockObject(NULL_BRUSH));
//b. 在CxxxView类中增加OnEraseBkgnd()消息响应函数,
// 将其中的代码改为: return TRUE;
// 直接返回TRUE表示告诉系统绘图时不再绘制背景,相当于设置窗口背景为NULL刷子.
//c. 为CxxxView类增加一个成员函数void OnDrawMem(CDC &dcMem),
// 并将你以前写在OnDraw()中的代码,移到OnDrawMem()中去即可.
//
/////////////////////////////////////////////////////////////////////////
//1. 改变当前View窗口的背景为空刷子
::SetClassLong(this->m_hWnd, GCL_HBRBACKGROUND,
(LONG)(HBRUSH)::GetStockObject(NULL_BRUSH));
//2. 获取当前绘图区的宽度和高度
CRect rcClient;
this->GetClientRect(&rcClient);
int nWidth = rcClient.Width();
int nHeight= rcClient.Height();
//3. 创建一个和pDC兼容的内存DC: dcMem
CDC dcMem;
dcMem.CreateCompatibleDC(pDC); //pDC换成NULL也可以,指定为显示器
//创建一个位图对象, 其宽度和高度就用当前绘图区的nWidth 和nHeight
CBitmap bmp;
bmp.CreateCompatibleBitmap(pDC, nWidth, nHeight);
//将bmp选入到dcMem中, 只有选入了位图的dcMem才有地方绘图,画到指定的bmp位图上
CBitmap * pOldBit = dcMem.SelectObject(&bmp);
//4. 先用背景色将位图清除干净,这里我用的是白色作为背景
dcMem.FillSolidRect(0, 0, nWidth, nHeight, RGB(255, 255, 255));
//5. 执行真正的绘图代码, 如dcMem.MoveTo(……); dcMem.LineTo(……); 等等
OnDrawMem(&dcMem);
//6. 将dcMem中的图拷贝到pDC上进行显示. 关键点.
pDC->BitBlt(0, 0, nWidth, nHeight, &dcMem, 0, 0, SRCCOPY);
//7. 绘图完成后的清理
bmp.DeleteObject();
dcMem.DeleteDC();
}
void CRussiaView::OnDrawMem(CDC* pDC)
{
}
选修内容3:抓屏并保存
void CbjlDlg::SavePicture(CString sSaveFileName)
{
BeginWaitCursor();
BYTE *pPicData = NULL;
CDC *pDC = GetDC();
HDC hScrDC = pDC->GetSafeHdc();
HDC hMemDC = CreateCompatibleDC( hScrDC );
INT nBitsPixel = pDC->GetDeviceCaps(BITSPIXEL);
CRect rcWndRect;
GetClientRect(&rcWndRect);
INT nWidth = rcWndRect.right;
INT nHeight = rcWndRect.bottom;
DWORD nImageSize = nWidth * nHeight * nBitsPixel / 8;
pPicData = new BYTE[nImageSize];
BITMAPINFOHEADER BmpInfoHead;
memset(&BmpInfoHead,0,sizeof(BmpInfoHead));
BmpInfoHead.biSize = sizeof(BmpInfoHead);
BmpInfoHead.biWidth = nWidth;
BmpInfoHead.biHeight = nHeight;
BmpInfoHead.biBitCount = nBitsPixel;
BmpInfoHead.biCompression = BI_RGB;
BmpInfoHead.biPlanes = 1;
BmpInfoHead.biSizeImage = nImageSize;
// 创建一个与屏幕设备描述表兼容的位图
HBITMAP hNewBit = CreateCompatibleBitmap( hScrDC, nWidth, nHeight);
// 把新位图选到内存设备描述表中
HBITMAP hOldBitmap = (HBITMAP)SelectObject( hMemDC, hNewBit );
// 把屏幕设备描述表拷贝到内存设备描述表中
StretchBlt(hMemDC, 0, 0, nWidth, nHeight, hScrDC, 0, 0, nWidth, nHeight, SRCCOPY);
//取得位图数据
GetDIBits( hMemDC, hNewBit,
0, nHeight,
pPicData, (LPBITMAPINFO)&BmpInfoHead,
DIB_RGB_COLORS);
//得到屏幕位图的句柄
SelectObject(hMemDC, hOldBitmap);
//释放
ReleaseDC(pDC);
DeleteDC(hMemDC);
DeleteObject(hNewBit);
if (pPicData)
{
DWORD nLineLen = (nWidth * nBitsPixel + 31) / 32 * 4;
DWORD nColSize = sizeof(RGBQUAD)*((nBitsPixel <= 8) ? 1<<nBitsPixel : 0);
DWORD nImageSize = sizeof(BITMAPINFOHEADER) + nColSize +
(DWORD)(UINT)nLineLen*(DWORD)(UINT)nHeight;
BITMAPFILEHEADER BmpFileHead;
BmpFileHead.bfType = MAKEWORD(‘B‘,‘M‘);
BmpFileHead.bfSize = sizeof(BITMAPFILEHEADER) +
sizeof(BITMAPINFOHEADER) + nImageSize;
BmpFileHead.bfReserved1 = BmpFileHead.bfReserved2 = 0;
BmpFileHead.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);
BITMAPINFOHEADER BmpInfoHead;
memset(&BmpInfoHead, 0, sizeof(BITMAPINFOHEADER));
BmpInfoHead.biSize = sizeof(BITMAPINFOHEADER);
BmpInfoHead.biWidth = nWidth;
BmpInfoHead.biHeight = nHeight;
BmpInfoHead.biBitCount = nBitsPixel;
BmpInfoHead.biCompression = BI_RGB;
BmpInfoHead.biPlanes = 1;
BmpInfoHead.biSizeImage = nImageSize - sizeof(BITMAPINFOHEADER) - nColSize ;
BmpInfoHead.biXPelsPerMeter = 0 ;
BmpInfoHead.biYPelsPerMeter = 0 ;
BmpInfoHead.biClrUsed = (nBitsPixel <= 8) ? 1<<nBitsPixel : 0;
BmpInfoHead.biClrImportant = 0 ;
CFile file;
if (file.Open(sSaveFileName, CFile::modeCreate | CFile::modeWrite))
{
file.Write(&BmpFileHead,sizeof(BmpFileHead));
file.Write(&BmpInfoHead,sizeof(BmpInfoHead));
file.Write(pPicData, nImageSize);
file.Close();
}
delete []pPicData;
}
EndWaitCursor();
}
选修内容4:使用路径层技术绘制复杂的图形
pDC->Rectangle(50, 50, 150, 150);
//pDC->Rectangle(90, 90, 200, 200);
pDC->BeginPath();
pDC->Rectangle(50, 50, 150, 150);
pDC->EndPath();
pDC->SelectClipPath(RGN_DIFF);
pDC->Rectangle(90, 90, 200, 200);