Windows学习笔记11——图形基础<三>

来源:互联网 发布:python实现支付宝 编辑:程序博客网 时间:2024/06/07 14:42

GDI映射方式

      映射方式的作用:定义了windows如何将GDI函数中指定的逻辑坐标映射为设备坐标

1、概述

       <1>、在TextOut中,以及在几乎所有的GDI函数中,坐标值使用的都是一种逻辑单位,windows必须将逻辑单位转换为设备单位,即图素,这种转换由映射方式、窗口原点、视口原点、窗口范围、视口范围控制。映射方式还指示者x轴、y轴的方向。

       <2>、Windows定义了8种映射方式:   MM_TEXT、MM_LOMETRIC、MM_HIMETRIC、MM_LOENGLISH、MM_HIENGLISH、MM_TWIPS、MM_ISOTROPIC、MM_ANISOTROPIC。

       <3>、调用SetMapMode函数来设置映射方式:SetMapMode(hdc, iMapMode);

        调用GetMapMode函数来得到当前的映射方式:iMapMode = GetMapMode(hdc);

       <4>、系统内定的映射方式是:MM_TEXT,在这种映射方式下,逻辑单位与实际单位相同,用户可以直接以图素为单位进行操作。

        在MM_TEXT映射模式下调用TextOut: TextOut(hdc, 8, 16, TEXT("Hello"),5); 表示文字从距离显示区域左端8图素、距离显示区域上端16图素的位置处开始;

        在MM_LOENGLISH映射模式下调用TextOut: TextOut(hdc, 50, -100, TEXT("Hello"),5); 表示文字从距离显示区域左端0.5英寸、距离显示区域上端1英寸的位置处开始

       <5>、用户如果需要以英寸或毫米显示图像,可以从GetDeviceCaps中取得所需要的信息,然后自己在进行缩放。其他映射方式只是避免用户自己缩放的一种简便方式。

2、设备坐标和逻辑坐标

       由于映射方式是一种设备描述表属性,所以,只有对需要设备描述表句柄作参数的GDI函数,映射方式才会起作用。Windows对所有消息(如WM_MOVE、WM_SIZE和WM_MOUSEMOVE),对所有的非GDI函数,甚至对一些GDI函数,永远使用设备坐标(即图素单位)。

      注意:从GetTextMetrics调用中返回的TEXTMETRIC结构的值是使用逻辑单位的。如使用MM_TEXT,从该结构得到的字元高度和宽度是图素单位(该模式下逻辑单位与设备单位一样),如果使用MM_LOENGLISH,则字元尺寸的单位是百分之一英寸。

3、设备坐标系

       windows将GDI函数中指定的逻辑坐标值映射为设备坐标。

       Windows为显示器定义了3种设备坐标系,所有的设备坐标系都以图素为单位,水平轴(x轴)上的值从左向右递增,垂直轴(y轴)上的值从上向下递增。

       屏幕坐标系:屏幕左上角为原点,如果以DISPLAY为参数调用CreateDC,来取得整个屏幕的设备描述表,则在内定的逻辑坐标将映射为屏幕坐标。

       窗口坐标系:以程序窗口为基准,如标题列、功能列、卷动列和窗口框架都包含在内,窗口边框的左上角为原点,如果使用GetWindowDC获得设备描述表,GDI函数中的逻辑坐标将被转换为窗口坐标。

       客户区坐标系:客户区的左上角为原点,GetDC或BeginPaint获得设备描述表,GDI函数中的逻辑坐标将被映射为客户区坐标。

        坐标系之间的转换:

        调用ClientToScreen函数,可以将客户区坐标转换为屏幕坐标

        调用ScreenToClient函数,可以将屏幕坐标转换为客户区坐标

        窗口坐标系很少使用,不过经常使用GetWindowRect函数取得屏幕坐标系下窗口的位置和大小。

4、窗口(windowport)与视口(viewport)

         自己理解:“窗口”是逻辑坐标下的程序视窗范围,“视口”是设备坐标下的程序视窗范围。

        注意设备坐标点(0,0),始终是在屏幕(或窗口或客户区)的左上角,但是点(0,0)不一定是映射关系中的原点。

        映射方式用于定义从“窗口”(逻辑坐标)到"视口"(设备坐标)的映射。

        窗口和视口仅是数学上的概念,要记住“窗口”是依据逻辑坐标的,单位可以是图素、毫米、英寸或者其他单位。“视口”是依据设备单位的,单位只能是图素。它们存在以下映射关系:

       

       其中,点(xWindow, yWindow)是待转换的逻辑点,点(xViewport,yViewport)是转换后的设备坐标点。(xWinExt, yWinExt)是逻辑坐标的窗口范围,(xViewExt, yViewExt)是设备坐标系下的窗口范围。

       注:调用DPtoLP函数,可以将设备坐标点转换为逻辑坐标点;

               DPtoLP(hdc, pPoints, iNumber);

               调用LPtoDP函数,可以将逻辑点转换为设备点:

               LPtoDP(hdc, pPoints, iNumber);

               经常使用的调用:将GetClientRect(设备坐标)取得的客户区大小转换为逻辑坐标:

               GetClientRect(hwnd, &rect);

               DPtoLP(hdc, (PPOINT)&rect, 2);

5、MM_TEXT映射方式

        这种映射方式又称为文字映射方式,视口范围与窗口范围的比例为1:,映射公式如下:

         xViewport = xWindow - xWinOrg + xViewOrg;

         yViewport = yWindow - yWinOrg + yViewOrg;

        Windows提供SetViewportOrgEx和SetWindowOrgEx函数来改变视口和窗口的原点。注意:一般只会使用SetViewportOrgEx和SetWindowOrgEx之一,但不会同时使用它们。

        例如:(1)、假设客户区为cxClient个像素宽,有cyClient个像素高,如果将逻辑点(0,0)映射到客户区的中心,可以调用:SetViewportOrgEx(hdc, cxClient/2, cyClient/2);

        这时,客户区中逻辑x轴的范围是-cxClient到cxClient,逻辑y轴的范围是-cyClient到cyClient;

        注意:使用SetWindowOrg(hdc, -cxClient/2, -cyClient/2, NULL);可以得到相同的效果。该函数是将逻辑点(-cxClient, -cyClient)设备点(0, 0)即左上角。注意这里的参数是逻辑单位。

        问题1:屏幕(或窗口或客户区)的左上角始终是设备点(0,0),与通过SetWindowOrg来改变窗口原点是否矛盾?

        答:虽然SetViewportOrgEx和SetWndowOrgEx是改变视口和窗口的原点,但是可以这样理解,SetViewportOrgEx(hdc, cxClient/2, cyClient/2)是改变逻辑原点,即将原来逻辑坐标系中的点(cxClient/2, cyClient/2)作为新的逻辑坐标系中的原点(0,0),SetWindowsOrgEx(hdc, -cxClient/2,-cyClient/2,NULL),实际上并未改变设备坐标系原点的位置,它改变的仍然是逻辑原点,它将逻辑点(-cxClient/2,-cyClient/2)映射到设备原点(0,0)即左上角,等价于原来的逻辑原点(0,0)变为新逻辑坐标系中的点(-cxClient/2,-cyClient/2)。

        问题2:既然这样,那么根据映射公式,“窗口”原点始终映射到"视口"原点的现象又该如何解释?

        答:首先,在同一个公式中描述的坐标是在同一个坐标系下的,例如逻辑坐标系的原点在公式中描述为逻辑点(xWinOrg,yWinOrg),而设备坐标系的原点在公式中描述为逻辑点(xViewOrg,yViewOrg);例如,我们将逻辑坐标系的原点设置为(cxClient,cyClient),在公式中描述为(cxClient,cyClient),设备坐标系的原点在设备点(0,0),在公式中描述为逻辑点(-cxClient,-cyClient),即现在点(cxClient,cyClient)到点(-cxClient,-cyClient)的映射就表示了逻辑坐标系到设备坐标系中的映射,因此,我们可以说新逻辑坐标系的原点映射到了设备坐标系的原点(该点在逻辑坐标下描述为(-cxClient,-cyClient),在设备坐标系中描述为设备点(0,0))。

        问题3:屏幕(或窗口或客户区)左上角的点与原点有什么关系?

        答:设备坐标系原点始终在这个左上角,它在设备坐标系中的描述始终是(0,0),在逻辑坐标系中的描述却可以是任意逻辑点(这取决于逻辑坐标系的原点位置);

        逻辑坐标系原点与这个左上角没有必然关系,只不过在MM_TEXT模式下,逻辑坐标系原点与设备坐标系原点重合,都在屏幕左上角。

       注意:记住以下两点,将非常重要:

        SetViewportOrgEx(hdc, cxClient/2,cyClient/2,NULL);//将逻辑原点平移(cxClient/2,cyClient/2)距离(矢量);

        SetWindowOrgEx(hdc, -cxClient/2, cyCLient/2, NULL);//将逻辑原点平移(cxClint/2,cyCLient/2)距离(矢量);

      因此,当两个函数调用同时使用时,逻辑原点平移的距离是(cxClient,cyClient)。

      得到当前的视口和窗口原点:

             GetViewportOrgEx(hdc, &pt);//返回值为设备坐标。

             GetWindowOrgEx(hdc, &pt);//返回值为逻辑坐标。

6、“度量”映射方式

       *Windows包含五种以实际尺寸来表示逻辑坐标的映射方式。由于x轴和y轴的逻辑坐标映射为相同的实际单位,这些映射方式能使我们画出不变形的圆和矩形。

       *这五种“度量”映射方式是:MM_LOENGLISH、MM_LOMETRIC、MM_HIENGLISH、MM_TWIPS、MM_HIMETRIC。注意:这五种映射方式的y轴是随着上升而增长的。

       *关于视口与窗口的范围:

       假定在windowsNT系统下,屏幕分辨率是1024*768,则视口与窗口的范围如下表:

      

       其中视口范围单位是:设备单位(图素),窗口单位是:逻辑单位(具体的英寸、毫米等)。

       视口范围与窗口范围的比例,反映每逻辑单位内图素的个数。

       *注意:当把映射方式改为这五种之一时,初始的窗口原点在屏幕左上角,x向右增加;y轴向上增加。(显示文字必须使用负的y轴值)。

       *改变逻辑坐标原点:在这些映射模式下使用SetViewportOrgEx来改变原点很方便。

       如:SetViewportOrgEx(hdc, 0, cyClient, NULL);//假定cyClient是以图素为单位的显示区域高度。

        另一种方法,通过SetWindowOrgEx函数来改变逻辑坐标原点,因为它的参数要求使用逻辑单位,因此要先将图素单位描述的点通过DPtoLP函数转换为逻辑点,然后再使用:

       如:pt.x = cxClient/2;  pt.y = cyClient/2;

              DPtoLP(hdc, &pt, 1);

              SetWindowOrgEx(hdc, -pt.x, -pt.y, NULL);

7、“自行决定的”映射方式

       MM_ISOTROPIC和MM_ANISOTROPIC,只有这两种映射方式可以让用户来改变视口和窗口范围。单词“isotropic”的意思是“同方向性”,“anisotropic”的意思是“异方向性”。

       MM_ISOTROPIC映射方式,x轴和y轴的逻辑单位的实际尺寸相等。它与“度量”映射方式之间的区别是:使用MM_ISOTROPIC,用户可以控制逻辑单位的实际尺寸。可以根据显示区域的大小来调整逻辑单位大小,并相应地放大或缩小显示区域内所画的图像。

       注:MM_TEXT和“度量”映射方式,称为“完全局限性”映射方式,即视口、窗口的范围不能改变,以及Windows将逻辑坐标换算为设备坐标的方法不能改变;

             MM_ISOTROPIC是“半局限性”映射方式,即视口、窗口的范围允许用户改变,但是Windows只会调整它们,使得x轴和y轴的逻辑单位代表同样的实际尺寸;

             MM_ANISOTROPIC是“非局限性”映射方式,即视口、窗口的范围允许用户改变,而且Windows不会调整这些值。

       <1>、MM_ISOTROPIC映射方式:

       在这种映射方式下,用户可以通过SetWindowExtEx和SetViewportExtEx来设定窗口、视口的范围,然后Windows将调整范围的值,以保证x轴与y轴上的逻辑单位有相同的实际距离。

       一般说来,用用户期望的逻辑窗口的逻辑尺寸作为SetWindowExtEx的参数,用显示区域的实际宽和高作为SetViewportExtEx的参数。为了更有效地使用显示区域的空间,必须先调用SetWindowExtEx,然后调用SetVieportExtEx函数。

       <2>、MM_ANISOTROPIC映射方式:

       在MM_ANISOTROPIC映射方式下,Windows不调整用户设定的视口、窗口范围,逻辑x轴和y轴可以具有不等的实际尺寸。

 

二、矩形、区域、剪切

      “区域”是屏幕上的一块地方,它是矩形、多边形、椭圆的组合。

1、矩形函数

       FillRect(hdc, &rect, hBrush);//用指定的画刷来填入矩形,该函数不需要先将画刷选进设备描述表。

       FrameRect(hdc, &rect, hBrush);//使用画刷画矩形框,但不填入矩形。(允许使用者画一个不一定为纯色的矩形框)

       InvertRect(hdc, &rect);//将矩形中所有图素翻转(1变为0, 0变为1)

       SetRect(&rect, xLeft, yTop, xRight, yBottom);//设置矩形的左上角与右下角坐标。

       OffsetRect(&rect, x, y);//将矩形沿x轴和y轴移动几个单位。

       InflateRect(&rect, x, y);//增减矩形的尺寸。

       SetRectEmpty(&rect);//矩形各栏位设置为0。

       CopyRect(&DestRect, &SrcRect);//将矩形复制给另一个矩形。

       IntersectRect(&DestRect, &ScrRect1, &ScrRect2);//取得两个矩形的交集

       UnionRect(&DestRect, &ScrRect1, &ScrRect2);//取得两个矩形的并集

       bEmpty = IsRectEmpty(&rect);//确定矩形是否为空

       bInRect = PtInRect(&rect, point);//确定点是否在矩形内

       矩形对象可以直接相互赋值,如DestRect = ScrRect;

2、随机矩形的实现(PeekMessage的使用)

      <1>、如何利用程序的空闲时间

      程序一定有许多空闲时间,在这个时间里,所有消息队列为空,Windows只停在一个小循环中等待键盘uozhe鼠标输入。要想利用这些空闲时间,可以使用PeekMessage函数。

      <2>这里着重介绍PeekMesage的使用

      函数原型:BOOL PeekMessage(LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax, UINT wRemoveMsg );

       PeekMessage的最后一个参数指定如何处理消息:

              PM_NOREMOVE:消息在被PeekMessage处理后,不从消息队列中移除

              PM_REMOVE:消息在被PeekMessage处理后,从消息队列中移除

       PeekMessage的返回值:

              消息队列中有有效消息时,返回非零;消息队列中没有有效消息时返回零。

       PeekMessage和GetMessage的对比:

       相同点:PeekMessage和GetMessage都用于查看应用程序的消息队列,有消息时将队列中的消息派发出去。

       不同点:无论应用程序消息队列中是否有消息,PeekMessage函数都立刻返回,程序能够继续执行后面的语句(无消息时执行其他指令,有消息时,先将消息派发出去,再执行其他指令);

       GetMessage函数只有在消息队列中有消息时才返回,对列中无消息时会一直等待,直到下一个消息出现时才返回,在这段等待时间里,应用程序不执行任何指令。

       用PeekMessage改写普通的消息循环:

       改写前: while(GetMessage(&msg, NULL, 0, 0))

                      {

                            TranslateMessage(&msg);

                            DispatchMessage(&msg);

                      }

                      return msg.wParam;

       改写后:

                 while(true)

                 {

                        if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))

                        {

                              if(msg.message == WM_QUIT)

                                    break;

                              TranslateMessage(&msg);

                              DispatchMessage(&msg);

                        }

                        else

                        {

                               //这是程序空闲时间,可以做想要做的事情

                        }

                 }

                return msg.wParam;

3、建立与绘制剪切区域

       剪切区域可以用于绘制和剪切,通过将剪切区域选进设备描述表,就可以用剪切区域来进行剪切(也就是说,将绘图的范围限制在显示区域的一部分)。

       注意:与画笔、画刷、点阵图一样,剪切区域也是GDI物件,在最后要用DeleteObject来删除使用者建立的剪切区域。

      *建立矩形剪切区域:

               hRgn = CreateRectRgn(xLeft, yTop, xRight, yBottom);

               或者hRgn = CreateRectRgnIndirect(&rect);

      *建立椭圆剪切区域:

               hRgn = CreateEllipticRgn(xLeft, yTop, xRight, yBottom);

               或者hRgn = CreateEllipticRgnIndirect(&rect);

      *建立圆角矩形剪切区域:

               hRgn = CreateRoundRectRgn(xLeft,yTop, xRight,yBottom, nWidth, nHeight);

      *建立多边形剪切区域:

               hRgn = CreatePolygonRgn(&point, iCount, iPolyFileMode);

      *组合剪切区域

              iRgnType = CombineRgn(hDestRgn, hSrcRgn1, hSrcRgn2, iCombine);

        hSrcRgn1,hSrcRgn2标识两个将要组合的区域,hDestRgn指向组合后的新区域,iCombine标识组合的方式,可以是如下值:RGN_AND:两个剪切区域的公共部分;RGN_OR:两个剪切区域的全部;RGN_XOR:两个剪切区域的全部除去公共部分; RGN_DIFF:hSrcRgn1 不在hSrcRgn2中的部分; RGN_COPY:hSrcRgn1的全部(忽略hSrcRgn2)。

      返回值:NULLREGION, 或SIMPLEREGION, 或COMPLEXREGION, 或ERROR。

     剪切区域句柄可以用于的四个函数:

     FillRgn, FrameRgn,  InvertRgn, PaintRgn;

     其中PaintRgn函数用设备描述表中当前画刷填入选定区域。

    注意:这些函数都假定区域是用逻辑坐标定义的

4、矩形与区域的剪切

      *类似于InvlidateRect和ValidateRect函数,Windows还有两个作用于区域的函数:

             InvalidateRgn(hwnd, hRgn, bErase ); 和 ValidateRgn(hwnd, hRgn);

      注意:当无效区域引起WM_PAINT消息时,剪切区域不一定是矩形。

      *将剪切区域选进设备描述表:(该剪切区域使用设备坐标)

             SelectObject(hdc, hRgn);  或 SelectClipRgn(hdc, hRgn);

      其他几个函数:

            ExcludeClipRect函数:将一个矩形从剪切区域中排除掉;

            IntersectClipRect 函数:用于建立一个新的剪切区域,它是前一个剪切区域与一个矩形的交集;

            OffsetClipRgn函数:用于将剪切区域移动到显示区域的另一部分。

5、实例

       创建两个椭圆区域:

             HRGN hRgnTemp1 = CreateEllipticRgn(0, 10, 20, 30);

             HRGN hRgnTemp2 = CreateEllipticRgn(12,10, 35,50);

       将两个区域组合:

             HRGN hRgnClip = CreateRectRgn(0,0,1,1);

             CombineRgn(hRgnClip, hRgnTemp1, hRgnTemp2, RGN_OR);

        删除原来的两个区域(因为不再需要了)

              DeleteObject(hRgnTemp1);

              DeleteObject(hRgnTemp2);

       这时,在响应WM_PAINT消息时,可以将组合后的剪切区域选进设备描述表:

              SelectClipRgn(hdc, hRgnClip);

      注意:使用BeginPaint获得设备描述表时,已经限制只能在剪切区域内绘图,因此用户不用关心绘制的图形会超出剪切区域边界,这样就可以画出在复杂的剪切区域内呈现的图画。

0 0
原创粉丝点击