现在的位置: 首页 > 综合 > 正文

深入分析MFC之GDI原理透析

2014年06月10日 ⁄ 综合 ⁄ 共 5500字 ⁄ 字号 评论关闭

GDI阐述

GDI :Graph device interface  图形设备接口,应用程序调用图形编程的接口

为什么会使用GDI呢?

首先要理解显示器如何进行图像的显示,一般显示器把将要显示的内容存放在显存中(集成显卡一般使用内存,而独立显卡现在基本都使用独显),framebuffer就是用来存储显示的数据,由DMA自动读取数据将其显示在窗口中。而这里显示的只是一个个的像素点,通常是先显示一行的像素点,发出水平同步信号后,再显示下一行,当整个一屏幕的数据显示完全后,才会发出垂直同步信号,发出下一帧数据。

注意:这里显示任何图像,包括点,线,多边形,窗口等等,其实都是由像素一点点画出来的。

关键对于编程人员而言必须要懂得图形硬件接口的操作,就是必须懂得如何驱动底层,每当更换硬件或者升级硬件时,就需要重新写驱动接口,这对于上层应用的人而言,更无异于天灾人祸,因为很多上层开发人员并不懂得底层操作,而且底层图形设备千奇百怪,所以提供一个设备无关的接口是必须的。GDI就是这种有微软主推的图形接口,当然OpenGL等也可以的,现在主要探讨GDI,GDI其实也是一种二次封装,在底层驱动或者操作系统层次上的一个类库,SDK本身就提供了接口,而MFC又做了一次封装,使其操作更方便而起,而且更健壮。

注意:这里函数显示的时候并不是直接进行显示,而是将要显示的数据存储在设备描述表中处理好一个即将要显示的逻辑数据,然后再将此数据显示到窗口中,这样一来,可以处理字体,字型,颜色,不同窗口重叠显示等等

MFC如何使用GDI?

首先对于GDI而言,他并不是将图像直接显示在设备上,而是将显示的数据首先绘制在一个设备描述表DC中,这里面存储了显示图像的描述字段,包括物理设备和各种状态信息。画图前,先取得设备描述表句柄,然后再调用GDI输出函数进行输出并将句柄返回给GDI。

注意:其实GDI已经完成了数据流过程,就是将要显示的数据已经组织好,并放入指定位置,告诉DMA到这里取数据,然后显示。

MFC中只需先获取设备描述表对象指针,然后调用绘图函数即可。这又是什么意思呢?其实就是我要将数据流中间截断,把我想显示的数据放入,这样就可以输出我的数据。如何做呢?就是要获取数据流中对象,也就是设备描述表句柄,把他勾出来,使用他提供的函数进行绘制图像,然后把句柄再返回给GDI,有借有还再借不难,况且他人也要用这个句柄。看起来有点麻烦,其实MFC给我做好了的,CDC类直接将设备描述表和获取设备描述表句柄的GDI函数封装在一起了,我们只管使用。

那有那些设备描述句柄对象呢?

MFC提供了CDC类,如下一个例子

	CDC* pDC = GetDC();
	CRect rect;
	GetClientRect(&rect);
	pDC->DrawText(_T("hello MFC!"), &rect, DT_SINGLELINE| DT_CENTER | DT_VCENTER);
	ReleaseDC(pDC);

这里例子主要是显示一行数据”hello MFC“,这里声明了对象还需要释放,而GetDC只能在窗口客户区画图,GetWindowDC可以在窗口的任一地方画图,包括非客户区菜单栏,工具栏。另外在OnPaint函数中不能使用上面的获取和释放函数,使用如下的方式:

	PAINTSTRUCT ps;
	CDC *pDC = BeginPaint(&ps);
	pDC->Ellipse(100,100,400,400);
	EndPaint(&ps);

看到这里,发现获取方式设备描述表句柄好像多了,的确,幸亏MFC提供了如下分类的类:

CPaintDC :窗口客户区画图(OnPaint)

CClientDC:窗口客户区画图(非OnPaint)

CWindowDC:窗口任意地方画图(包括非客户区)

CMetaFileDC:GDI元文件画图(暂略)

注意:1)使用CPaintDC比使用::BeginPaint和::EndPaint好,因为在绘图工作下,不能将该消息从消息队列中删除,当应用程序处理WM_PAINT消息失败时会陷入死循环,而CPaintDC不会。

          2)在使用CClientDC和CWindowDC可以在全屏幕窗口下画图,如下方式

                  CClientDC dc(NULL);//CWindowDC  dc(NULL);
                  dc.Ellipse(0, 0, 1440, 900); //在默认映射模式下,1个单位对应一个像素点,而我的显示器分辨率正好是1440*900

设备描述表属性

在之前的字体显示中,我们采用的都是默认的 画笔(1个像素点宽的黑实线),默认的画刷(单一的白色),默认的字体(12点高的普通比例字体),定义设备描述表属性的函数是SelectObject,可以进行画笔,画刷,字体,位图,调色板,区域的选取:如下,其他请参看MSDN中CDC类的有关文档

CDC::SetBkColor设置当前背景色。

CDC::SetBkMode设置背景模式。

CDC::SelectObject选择绘制对象如钢笔的GDI。

CDC::SelectStockObject选择Windows提供的某个预定义的库存钢笔、画笔或字体

绘图模式

在默认绘图模式下,使用的是直接将颜色复制到指定位置,不采用其他复合的运算,而CDC::SetROP2可以 设置其他的绘图模式,如下一些模式:

  • R2_BLACK 像素始终黑色。

  • R2_WHITE 像素始终为空。

  • R2_NOP 像素不变。

  • R2_NOT 像素是屏幕颜色的反向操作。

  • R2_COPYPEN 像素是钢笔颜色。 //默认使用模式

  • R2_NOTCOPYPEN 像素是钢笔颜色的反向操作。

  • R2_MERGEPENNOT 像素是钢笔颜色和屏幕颜色(最终像素= (不是屏幕像素)或笔)的反的组合。

  • R2_MASKPENNOT 像素是颜色组合共有的笔和屏幕(最终像素= (不是屏幕像素)和笔)的反向操作。

  • R2_MERGENOTPEN 像素是屏幕颜色和钢笔颜色(最终像素= (不是笔)或屏幕像素)的反的组合。

  • R2_MASKNOTPEN 像素是颜色组合共有的屏幕和笔(最终像素= (不是笔)和屏幕像素)的反向操作。

  • R2_MERGEPEN 像素是钢笔颜色和屏幕颜色(最终像素的组合=笔或屏幕像素为单位)。

  • R2_NOTMERGEPEN 像素是R2_MERGEPEN 颜色(最终像素的反=非(笔或屏幕像素)。

  • R2_MASKPEN 像素是颜色组合共有的笔和屏幕(最终像素=笔和屏幕像素为单位)。

  • R2_NOTMASKPEN 像素是R2_MASKPEN 颜色(最终像素的反=非(笔和屏幕像素)。

  • R2_XORPEN 像素是在笔或在屏幕颜色组合,但是,不在两个(最终像素=钢笔"异或"屏幕像素为单位)。

  • R2_NOTXORPEN 像素是R2_XORPEN 颜色(最终像素的反=非(钢笔"异或"屏幕像素)。

    如下在客户区会两条对角线,第一条为默认黑色,第二条为默认画笔颜色的反色,就是白色的线,因为默认背景色成灰色状态,所以这个还是可以看到的,如下

	CRect rect;
	GetClientRect(&rect);

	CClientDC dc(this);
	dc.SetBkMode(TRANSPARENT);
	dc.MoveTo(rect.left, rect.top);
	dc.LineTo(rect.right, rect.bottom);
	dc.SetROP2(R2_NOTCOPYPEN);
	dc.MoveTo(rect.left, rect.bottom);
	dc.LineTo(rect.right, rect.top);

映射模式

   默认情况一个1单位的逻辑尺寸正好对应一个像素,就是MM_TEXT映射模式,调用的函数为 CDC::SetMapMode,如下其他一些模式:

  • MM_ANISOTROPIC 逻辑单位转换为随机缩放的轴的任意单元。设置为MM_ANISOTROPIC 的映射模式不会更改当前窗口或视区设置。若要更改单元,orientation和缩放,调用SetWindowExt
    SetViewportExt 成员函数。

  • MM_HIENGLISH 每个逻辑单位转换为0.001英寸。正x是在右侧;正y启用。

  • MM_HIMETRIC 每个逻辑单位转换为0.01毫米。正x是在右侧;正y启用。

  • MM_ISOTROPIC 逻辑单位转换为方式调用的轴的任意单元,即沿X轴平移1个单位与沿y轴平移1个单位相等。使用SetWindowExt 和SetViewportExt 成员函数指定所需的单元测试和轴的orientation。GDI根据需要进行调整确保x和y单元保持大小相同。

  • MM_LOENGLISH 每个逻辑单位转换为0.01英寸。正x是在右侧;正y启用。

  • MM_LOMETRIC 每个逻辑单位转换为0.1毫米。正x是在右侧;正y启用。

  • MM_TEXT 每个逻辑单位转换为1设备像素。正x是在右侧;正y放置。 //默认映射模式

  • MM_TWIPS 每个逻辑单位转换为1/20点。(因为点是1/72英寸,twip是1/1440英寸。)正x是在右侧;正y启用。

	CClientDC dc(this);
	CRect rect;
	GetClientRect(&rect);
	//dc.SetMapMode(MM_TEXT);
	//dc.SetMapMode(MM_LOMETRIC); //此种模式下,y轴方向向上,设备原点不变还是左上角(0,0)
	dc.SetMapMode(MM_LOENGLISH); 
	dc.MoveTo(0,-100);
	dc.LineTo(2000,-100);

注意:当设备为非MM_TEXT映射模式下,需要主要他们的y轴方向是向上的,不是向下,坐标原点默认还是视口的(0,0)

看下面自己定义设备尺寸与视口尺寸的比例:

	dc.SetMapMode(MM_ANISOTROPIC);
	dc.SetWindowExt(500,500);
	dc.SetViewportExt(rect.Width(),rect.Height());
	CString str,strnew;
	str.Format(_T("OP(%d,%d):"),point.x,point.y);
	dc.DPtoLP(&point);
	strnew.Format(_T("NP(%d,%d)"),point.x,point.y);
	str += strnew;
	dc.TextOut(point.x,point.y, str);

	CRect rectObj(100,100, 500, 500);
	dc.Rectangle(&rectObj);
	dc.Ellipse(&rectObj);

这里可以进行比例的设备,逻辑尺寸定义大小就是长500,宽500的窗口,也就是说下面的绘图尺寸不能超过这个尺寸,否则真正的窗口无法进行显示,而真正的窗口就是默认的客户区的宽高,这样一来可能比例拉伸的情况,图片最能反映,以后当窗口尺寸发生变化,但是逻辑尺寸仍然以(0,0,500,500)这个矩形的尺寸内绘图。这里将坐标进行了显示,由于是在右键按下函数里面写的,默认传过来的是客户区尺寸,将它转换为设备尺寸,更能看见他们的区别。

坐标转换

    请见上面的例子,这里就是逻辑坐标与设备真正的坐标进行转换,注意区分窗口区坐标与客户区坐标,后两个主要是参考点不一样导致的,而前面主要是显示的原理不一样,逻辑坐标是我们处理数据的缓冲区,真正显示的是设备坐标。

获取设备信息

   
检索有关显示设备的各种形式设备特定的信息。
CDC::GetDeviceCaps,如下一些实例

	CClientDC dc(this);
	CRect rect;
	GetClientRect(&rect);
	int cx = dc.GetDeviceCaps(HORZRES); //显示器水平像素点数目
	int cy = dc.GetDeviceCaps(VERTRES);  //显示器垂直像素点数目
	int cx1 = dc.GetDeviceCaps(LOGPIXELSX); //水平方向每逻辑英寸内像素点的数目
	int cy1 = dc.GetDeviceCaps(LOGPIXELSY); //垂直方向每逻辑英寸内像素点的数目
	long numclrs = dc.GetDeviceCaps(NUMCOLORS);//静态的颜色数目
	long bitpix = dc.GetDeviceCaps(BITSPIXEL); //每个像素点用几位(bit)表示
	int planes = dc.GetDeviceCaps(PLANES); //位平面的数目
	dc.SetMapMode(TRANSPARENT);
	CString strxy;
	strxy.Format(_T("显示器分辨率:%d*%d"), cx, cy);
	dc.TextOut(0,0, strxy);
	strxy.Format(_T("dpix:%d, dpiy: %d"), cx1, cy1);
	dc.TextOut(0,20, strxy);
	strxy.Format(_T("numclrs:%d, bitclrs: %d, plans:%d"), numclrs, bitpix, planes);
	dc.TextOut(0,40, strxy);

当前使用分辨率为1440*900的19英寸显示器,如下得到的一些值:

dpi:是由于操作系统限制为96,修改这个值会发生变化;

numclrs:按理应该为2^24, 显示为-1,暂未搞懂;

bitclrs:因为真彩色使用RGB每个8位 ,就是需要24位,但是为了总线存取方便通常会使用32位(4个字节)来存储一个像素点;

planes:位平面暂未搞懂,回头再看。。。

抱歉!评论已关闭.