您的位置:首页 > 其它

Windows图形设备接口及Windows绘图

2017-05-10 15:54 441 查看

Windows图形设备及接口(GDI)

是为与设备无关的图形设计的。

设备无关

所谓设备的无关性,就是操作系统屏蔽了硬件设备的差异。因而设备无关性能够使用户编程时候,不必要考虑特殊的硬件设置。

比如说,你打印一个文档的时候,你在编辑这个文档过程中,并不需要去考虑我打印的时候需要用什么样的打印机,也就是说 这就是典型的设备无关性。

图形设备接口(GDI)

GDI(Graphics Device Interface)

GDI负责系统与用户或者绘图程序之间的信息交换,并控制在输出设备上显示图形或者文字,所以说,它是Windows系统的重要组成部分。

Windows利用GDI和Windows设备驱动程序,它来支持与设备无关的图形,那么这个时候开发人员只要建立与输出设备的关联,让系统加载相应的设备驱动程序就可以。而这个设备驱动程序,通常是由相应的设备设计厂商他们来提供的,

GDI的一些基本概念

设备描述表

设备描述表就是设备环境的属性的集合,它是应用程序与输出设备之间的桥梁,为确保图形输出的设备无关性。Windows系统并不允许用户直接访问外设的,而是Windows系统的提供统一的设备环境叫做Device Context,我们通常给它简称为DC。

然后使应用程序与设备相连,实际上这样的话,大大减轻了用户的开发的工作量。然后应用程序通过设备描述表的句柄,来间接地存取设备描述表及其属性,最后应用程序每一次的图形操作,都参照这个设备描述表中的属性来执行,从而达到了设备无关性。

画笔

绘图工具与颜色

画笔

画笔的操作,首先,我们要创建画笔,要把这个笔选入设备环境。就像大家画图的时候,这个笔如果放在桌子上这个笔不会自动画图,必须拿在你的手上才能画图。画完图后,这个笔就不需要了删除画笔。

那也就是说,首先我们要创建画笔,创建画笔的时候我们要事先定义一个画笔的句柄,实际上这个就是画笔的对象。画笔的句柄的关键词是HPEN,第一个字母为大写H的通常是句柄标识,H大写的后面一个PEN,马上就能识别出来这是一个画笔句柄。那么定义这个hP,这个就是画笔对象,然后我们调用函数GetStockObject,来获取Windows系统定义的画笔。

eg:

HPEN hp;
hp = (HPEN)GetStockObject(BLACK_PEN);
//Windows系统定义的四种画笔:
//WHITE_PEN 白色画笔
//BLACK_PEN 黑色画笔
//DC_PEN 用户区域用笔
//NULL_PEN 空的笔

/**创建新画笔**/
hp = CreatePen(
int nPenStyle,//确定画笔样式
int nWidth,//画笔宽度
COLORREF rgbColor//画笔颜色);
/*
画笔样式:
PS_DASH;     虚线
PS_DASHDOT;   点划线
PS_DASHDOTDOT; 双点划线
PS_DOT;       点线
PS_INSIDEFRAME;  实线
PS_NULL;      无
PS_SOLID;     实线
*/


创建画笔后,必须调用SelectObject函数将其选入设备环境。不再使用的时候,请注意把画笔删除掉,以免占用太多的内存。

常用绘图函数

MoveToEx()设置画笔当前位置

BOOL MoveToEx(
HDC hdc,
int x,y, //x,y分别为新位置的逻辑坐标
LPPOINT lpPoint //存放原画笔位置的POINT结构地址);


LineToEx()从当前位置向指定坐标点画直线

BOOL LineToEx(
HDC hdc,
int x,y //x和y为线段的终点坐标);


Polyline()从当前位置开始、依次用线段连接lpPoints指定的各个点

BOOL Polyline(
HDC hdc,
LPPOINT lpPoints, //指向包含各点坐标的POINT结构数组的指针
int nCount  //存放原画笔位置的POINT结构指针,这个Count参数, 是POINT数组中的点的个数);


Arc()绘制椭圆弧线

BOOL Arc
( HDC hdc,
int x1,int y1, //边框矩形左上角的逻辑坐标
int x2,int y2, //边框矩形右下角的逻辑坐标
int x3,int y3, //椭圆弧起点坐标
int x4,int y4  //椭圆弧终点坐标
);


Pie()绘制饼图,并用当前画刷进行填充

BOOL Pie
(
HDC hdc,
int x1,int y1, //边框矩形左上角的逻辑坐标
int x2,int y2, //边框矩形右上角的逻辑坐标
int x3,int y3, //椭圆弧起点坐标
int x4,int y4  //椭圆弧终点坐标
);


Rectangle()绘制矩形,并用当前画刷填充

BOOLRectangle
(
HDC hdc,
int x1, int y1,
int y2, int y2
);


Ellipse()绘制椭圆并用当前画刷填充

BOOL Ellipse
(
HDC hdc,
int x1, int y1,
int x2, int y2
);


BOOL RoundRect()绘制圆角矩形,并用当前画刷填充

BOOL RoundRect
(
HDC hdc,
int x1, int y1,//矩形左上角逻辑坐标
int x2, int y2,//矩形右下角逻辑坐标
int nHeight,
int nWidth //圆角的高度和宽度
);


实例_1



#include <Windows.h>    //包含应用程序所需要的数据类型和数据结构的定义
#include <stdlib.h>
#include <string.h>

//LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //窗口函数说明
long WINAPI WndProc(HWND hwnd, UINT iMessage, UINT wParam, LONG lParam);
BOOL InitWindowsClass(HINSTANCE hInstance); //窗口类的初始化
BOOL InitWindows(HINSTANCE hInstance, int nCmdShow);//窗口的初始化
char    lpszClassName[] = "窗口";     //窗口类名
char    lpszTitle[] = "My_Windows";     //窗口标题名

int WINAPI WinMain
1ac5a
(HINSTANCE hInstance,
HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)
{
MSG Message;

if (!InitWindowsClass(hInstance)) return FALSE;
if (!InitWindows(hInstance, nCmdShow)) return FALSE;

//消息循环
while (GetMessage(&Message, 0, 0, 0))
{
TranslateMessage(&Message);
DispatchMessage(&Message);
}
return Message.wParam;          //消息循环结束即程序终止时将消息返回系统
}

long WINAPI WndProc(
HWND hwnd,                  //哪一个窗口传来的消息
UINT iMessage,              //传过来的是什么消息
UINT wParam,                //这个消息的字参数是哪一些
LONG lParam                 //这个消息的长字参数是哪一些
)
{
HDC hDc;                //定义设备环境句柄
HBRUSH hBrush;          //定义画刷句柄
HPEN hPen;              //画笔句柄
PAINTSTRUCT PtStr;      //指向包含绘图信息的结构变量

POINT points[6] = { {100,262},{70,277},{70,300},{130,300},{130,277},{100,262}};
switch (iMessage)
{
case WM_PAINT:          //绘图消息
hDc = BeginPaint(hwnd, &PtStr);     //创建DC,把这个绘图结构在这个window里面,将它的绘图结构的信息取出来,放到设备环境里面去
hPen = (HPEN)(GetStockObject(NULL_PEN));    //选择空画笔,画线可以表示移动画笔起始位置
SelectObject(hDc, hPen);
hBrush = (HBRUSH)(GetStockObject(BLACK_BRUSH));
SelectObject(hDc,hBrush);
LineTo(hDc, 50, 50);
DeleteObject(hPen);
hPen = CreatePen(PS_SOLID, 2, RGB(255, 0, 0));
SelectObject(hDc, hPen);

//画三角形
LineTo(hDc, 150, 50);
LineTo(hDc, 100, 137);
LineTo(hDc, 50, 50);

//画一个圆
Arc(hDc, 50, 137, 150, 237, 100, 137, 100, 137);

//画一个五边形
Polyline(hDc, points, 6);

//填充圆角矩形
RoundRect(hDc, 213, 100, 287, 137, 20, 20);

//填充半圆
Pie(hDc, 213, 137, 288, 212, 240, 137, 260, 137);

//填充矩形
Rectangle(hDc, 213, 212, 287, 250);

DeleteObject(hPen);
DeleteObject(hBrush);
EndPaint(hwnd, &PtStr);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return(DefWindowProc(hwnd, iMessage, wParam, lParam));

}
return 0;
}

BOOL InitWindowsClass(HINSTANCE hInstance)
{
WNDCLASS wndclass;
wndclass.style = 0;                     //窗口类型为缺省类型
wndclass.lpfnWndProc = WndProc;         //定义窗口处理函数
wndclass.cbClsExtra = 0;                //窗口类无扩展
wndclass.cbWndExtra = 0;                //窗口实例无扩展
wndclass.hInstance = hInstance;         //当前实例句柄
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
//窗口的最小化图标为缺省图标
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
//窗口采用箭头光标
wndclass.hbrBackground = (HBRUSH)(GetStockObject(WHITE_BRUSH));
/*
将背景刷选入设备环境
窗口背景为白色。那么为了体现这个地方选的是背景刷,所以这里要有一个HBRUSH的强制类型转换这一点请注意
否则的话, 如果没有这个强制类型转换的话,系统可能不知道我获得的是笔还是刷
加强制类型转换
提示:不能将HGDIOBJ类型的值分配到HBRUSH类型的实体
typedef void __RPC_FAR* HGDIOBJ,typedef void __RPC_FAR* HBRUSH
定义一样,但是不同的数据类型,混用的话,类型检查的时候通不过
*/

wndclass.lpszMenuName = NULL;           //窗口中无菜单
wndclass.lpszClassName = lpszClassName; //窗口类名为“窗口”

return RegisterClass(&wndclass);
}

BOOL InitWindows(HINSTANCE hInstance, int nCmdShow)
{
HWND hwnd;          //定义窗口句柄
//创建窗口
hwnd = CreateWindow
(
lpszClassName,              //窗口类名
lpszTitle,                  //窗口实例的标题名
WS_OVERLAPPEDWINDOW,        //窗口的风格
CW_USEDEFAULT,
CW_USEDEFAULT,              //窗口左上角坐标为缺省值
CW_USEDEFAULT,
CW_USEDEFAULT,              //窗口的宽高为缺省值
NULL,                       //此窗口无父窗口
NULL,                       //此窗口无主菜单
hInstance,                  //创建此窗口的应用程序的当前句柄
NULL                        //不使用该值
);
if (!hwnd)
return FALSE;               //窗口初始化成功判断
ShowWindow(hwnd, nCmdShow);     //显示窗口
UpdateWindow(hwnd);             //刷新窗口
return TRUE;
}


实例_2

MM_TEXT


MM_ISOTROPIC



MM_ANISOTROPIC



#include <Windows.h>    //包含应用程序所需要的数据类型和数据结构的定义
#include <stdlib.h>
#include <string.h>
int nMode;
//LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM); //窗口函数说明
long WINAPI WndProc(HWND hwnd, UINT iMessage, UINT wParam, LONG lParam);
BOOL InitWindowsClass(HINSTANCE hInstance); //窗口类的初始化
BOOL InitWindows(HINSTANCE hInstance, int nCmdShow);//窗口的初始化
char    lpszClassName[] = "窗口";     //窗口类名
char    lpszTitle[] = "My_Windows";     //窗口标题名

int WINAPI WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow)
{
MSG Message;

if (!InitWindowsClass(hInstance)) return FALSE;
if (!InitWindows(hInstance, nCmdShow)) return FALSE;

//消息循环
while (GetMessage(&Message, 0, 0, 0))
{
TranslateMessage(&Message);
DispatchMessage(&Message);
}
return Message.wParam;          //消息循环结束即程序终止时将消息返回系统
}

long WINAPI WndProc(
HWND hwnd,                  //哪一个窗口传来的消息
UINT iMessage,              //传过来的是什么消息
UINT wParam,                //这个消息的字参数是哪一些
LONG lParam                 //这个消息的长字参数是哪一些
)
{
HDC hDc;                //定义设备环境句柄
HBRUSH hBrush;          //定义画刷句柄
HPEN hPen;              //画笔句柄
PAINTSTRUCT PtStr;      //指向包含绘图信息的结构变量

POINT points[6] = { {100,262},{70,277},{70,300},{130,300},{130,277},{100,262}};
switch (iMessage)
{
case WM_PAINT:          //绘图消息
hDc = BeginPaint(hwnd, &PtStr);     //创建DC,把这个绘图结构在这个window里面,将它的绘图结构的信息取出来,放到设备环境里面去
SetMapMode(hDc, nMode);             //设置映射模式
SetWindowExtEx(hDc, 300, 300, NULL);    //设置窗口区域
SetViewportExtEx(hDc, 300, 200, NULL);  //设置视口区域
SetViewportOrgEx(hDc, 300, 150, NULL);  //设置视口原点

hPen = (HPEN)(GetStockObject(NULL_PEN));    //选择空画笔,画线可以表示移动画笔起始位置
SelectObject(hDc, hPen);
hBrush = (HBRUSH)(GetStockObject(BLACK_BRUSH));
SelectObject(hDc, hBrush);
LineTo(hDc, 50, 50);
DeleteObject(hPen);
hPen = CreatePen(PS_SOLID, 2, RGB(255, 0, 0));
SelectObject(hDc, hPen);

//画三角形
LineTo(hDc, 150, 50);
LineTo(hDc, 100, 137);
LineTo(hDc, 50, 50);

//画一个圆
Arc(hDc, 50, 137, 150, 237, 100, 137, 100, 137);

//画一个五边形
Polyline(hDc, points, 6);

//填充圆角矩形
RoundRect(hDc, 213, 100, 287, 137, 20, 20);

//填充半圆
Pie(hDc, 213, 137, 288, 212, 240, 137, 260, 137);

//填充矩形
Rectangle(hDc, 213, 212, 287, 250);

DeleteObject(hPen);
DeleteObject(hBrush);
EndPaint(hwnd, &PtStr);
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_LBUTTONDOWN:        //单击鼠标左键
nMode = MM_ISOTROPIC;   //更改映射模式为MM_ISOTROPIC
InvalidateRect(hwnd, NULL, 1);  //刷新用户区
break;
case WM_RBUTTONDOWN:        //单击鼠标左键
nMode = MM_ANISOTROPIC; //更改映射模式为MM_ANISOTROPIC
InvalidateRect(hwnd, NULL, 1);  //刷新用户区
break;

default:
return(DefWindowProc(hwnd, iMessage, wParam, lParam));

}
return 0;
}

BOOL InitWindowsClass(HINSTANCE hInstance)
{
WNDCLASS wndclass;
wndclass.style = 0;                     //窗口类型为缺省类型
wndclass.lpfnWndProc = WndProc;         //定义窗口处理函数
wndclass.cbClsExtra = 0;                //窗口类无扩展
wndclass.cbWndExtra = 0;                //窗口实例无扩展
wndclass.hInstance = hInstance;         //当前实例句柄
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
//窗口的最小化图标为缺省图标
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
//窗口采用箭头光标
wndclass.hbrBackground = (HBRUSH)(GetStockObject(WHITE_BRUSH));
/*
将背景刷选入设备环境
窗口背景为白色。那么为了体现这个地方选的是背景刷,所以这里要有一个HBRUSH的强制类型转换这一点请注意
否则的话, 如果没有这个强制类型转换的话,系统可能不知道我获得的是笔还是刷
加强制类型转换
提示:不能将HGDIOBJ类型的值分配到HBRUSH类型的实体
typedef void __RPC_FAR* HGDIOBJ,typedef void __RPC_FAR* HBRUSH
定义一样,但是不同的数据类型,混用的话,类型检查的时候通不过
*/

wndclass.lpszMenuName = NULL;           //窗口中无菜单
wndclass.lpszClassName = lpszClassName; //窗口类名为“窗口”

return RegisterClass(&wndclass);
}

BOOL InitWindows(HINSTANCE hInstance, int nCmdShow)
{
HWND hwnd;          //定义窗口句柄
//创建窗口
hwnd = CreateWindow
(
lpszClassName,              //窗口类名
lpszTitle,                  //窗口实例的标题名
WS_OVERLAPPEDWINDOW,        //窗口的风格
CW_USEDEFAULT,
CW_USEDEFAULT,              //窗口左上角坐标为缺省值
CW_USEDEFAULT,
CW_USEDEFAULT,              //窗口的宽高为缺省值
NULL,                       //此窗口无父窗口
NULL,                       //此窗口无主菜单
hInstance,                  //创建此窗口的应用程序的当前句柄
NULL                        //不使用该值
);
if (!hwnd)
return FALSE;               //窗口初始化成功判断
ShowWindow(hwnd, nCmdShow);     //显示窗口
UpdateWindow(hwnd);             //刷新窗口
return TRUE;
}


映射模式

在Windows应用程序中,只要进行绘图,就要使用GDI坐标系统。Windows提供了几种映射方式,每一种映射都对应着一种坐标系。例如,绘制图形时,必须给出图形各个点在客户区的位置,其位置用x 和y两个坐标表示,x表示横坐标,y表示纵坐标。在所有的GDI绘制函数中,这些坐标使用的是一种“逻辑单位”。当GDI函数将结果输出送到某个物理设备上时,Windows将逻辑坐标转换成设备坐标(如屏幕或打印机的像素点)。本文讨论了图形环境中的各个映射模式,包括它们是什么,怎么工作的,以及它们真正的含义。

一、窗口、视口以及映射模式基本概念 

强调一个网上和教科书上没有将清楚但是至关重要的概念:窗口和视口其实是同一块矩形区域,两者坐标系的原点是同一个点。窗口和视口的区别仅仅是单位不同。窗口和视口都不是指显示屏或打印机上的区域,我们看到显示屏上的物体实际上是显示在“设备环境”上的,由于视口(也就是窗口)区域与设备环境的左手坐标系第一象限xoy平面有交集,我们才能看到视口中的物体。窗口的变换和视口的变换的目的是一样的,都是为了将物体显示在设备环境中,只不过由于视口中使用像素作为单位也就是显示屏的设备坐标,所以往往在视口中进行调整dc比较直观一些。

(一)逻辑坐标。逻辑坐标即是世界坐标系下的坐标。逻辑坐标与设备无关,客观世界中的场景可以由世界窗口或视口中进行描述,在窗口中进行描述时使用世界坐标系中的坐标单位也即逻辑坐标,在视口中进行描述使用的是设备坐标。 

(二)设备坐标。图形输出时,Windows将GDI函数中指定的逻辑坐标映射为设备坐标。在屏幕显示的设备坐标系统中,单位以像素点为准,水平值从左到右增大(正方向向右),垂直值从上到下增大(正方向向下)。注意设备坐标系的原点永远不会移动,它们仅仅与物理设备有关。

设备空间的范围实际显示设备上的矩形区域,通常有三种范围:窗口的客户区(使用BeginPaint 或 GetDC 获取)全窗口(使用GetWindowDC 获取)全屏幕(使用GetDc(0)/ CreateDC 获取);还有一些特殊的设备空间,如内存设备空间(使用CreateCompatibleDC获取),打印机设备空间,元文件设备空间即(CreateMetaFile)Windows中包括以下3种设备坐标,以满足各种不同需要:  

  1、客户区域坐标,包括应用程序的客户区域,客户区域的左上角为(0, 0)。 

  2、屏幕坐标,包括整个屏幕,屏幕的左上角为(0, 0)。屏幕坐标用在WM_MOVE消息中(对于非子窗口)以及下面的Windows 函数中:CreateWindow 和MoveWindow(都对于非子窗口)、GetMessage、GetCursorPos、GetWindowRect、WindowFromPoint 和SetBrushOrg 中。用函数ClientToScreen和ScreenToClient可以将客户区域坐标转换成屏幕区域坐标,或反之。

  3、全窗口坐标,包括一个程序的整个窗口,包括标题条、菜单、滚动条和窗口框,窗口的左上角为(0,0)。使用GetWindowDC得到的窗口设备环境,可以将逻辑单位转换成窗口”坐标。

(三)映射模式。映射方式定义了Windows如何将GDI函数中指定的逻辑坐标映射为设备坐标。Windows为了程序员方便,允许程序员在一个假想的空间上(世界坐标系中)绘制图形,这就是逻辑空间,但是最终这些图形还是需要显示到真实的屏幕或打印机,这就是设备空间。此时就会出现一个问题,即如何将逻辑空间中的图形通过怎样的关联适当的显示在屏幕上呢?Windows给出了答案—映射模式,Windows内定了8种映射模式,其中有常用的6种固定的模式(MM_TEXT、MM_LOMETRIC、MM_HIMETRIC、MM_LOENGLISH、MM_HIENGLISH、MM_TWIPS)把逻辑单位及数轴的方向都确定好了,还有2种允许自行定义逻辑单位及数轴方向。这两种模式的区别在于MM_ISOTROPIC要求必须横纵两轴的逻辑单位必须是相等的,而MM_ANISOTROPIC是绝对的自定义,没有任何限制。设备坐标系始终是左手坐标系且以显示区域的左上角的(0,0)点作为坐标原点,水平向右为x轴的正方向,垂直向下为y轴的正方向。窗口坐标系(就是视口坐标系)是可以自定义的坐标系,其坐标轴和原点都不固定,dc绘图始终是在窗口坐标系中进行。在MM_TEXT映射模式下,窗口坐标系的x轴向右,y轴向下和设备环境的坐标系方向相同,在没有移动窗口坐标系的原点时,窗口坐标系的原点也在设备点(0,0)处。在窗口坐标系的第一象限绘图,图像会显示在设备环境中。这开始给我一种错觉:视口就是设备环境的显示区域。 需要明确在MM_TEXT的映射模式下,视口坐标系(画笔)的x轴与设备环境坐标系(显示屏)的x轴平行都是水平向右为正,视口坐标系(画笔)的y轴与设备环境坐标系(显示屏)的y轴平行都是垂直向下为正。此时将dc从世界坐标系的原点(0,0)移动到(X,Y)后,dc绘制的图形会直接显示的设备环境(显示屏)上。

在MM_LOMETRIC、MM_HIMETRIC、MM_LOENGLISH、MM_HIENGLISH、MM_TWIPS的映射模式下,窗口坐标系的x轴向右,y轴向上。所以在窗口的第一象限绘图一般图像不会显示在设备环境上(如窗口的客户区),因为此时视口(窗口)的第一象限与设备环境的第一象限的交集为空。





//所有像素数
int pagecx=dc.GetDeviceCaps(HORZRES);
int pagecy=dc.GetDeviceCaps(VERTRES);
//即每英寸点数
short cxInch = dc.GetDeviceCaps(LOGPIXELSX);
short cyInch = dc.GetDeviceCaps(LOGPIXELSY);
// 计算一个设备单位等于多少0.1mm
double scaleX = 254.0 / (double)GetDeviceCaps(dc.m_hAttribDC,LOGPIXELSX);
double scaleY = 254.0 / (double)GetDeviceCaps(dc.m_hAttribDC, LOGPIXELSY);
说明:
主要用到的参数见例子中的:HORZRES,VERTRES,LOGPIXELSX,LOGPIXELSY.总的来说是为了方便控制打印或重画时的控制,如为了定制打印时,一般依据的是物理的长度,而不是像素,而DC一般是用像素的映射模式,所以需要一下转换,上面这个函数就为这种转换设计的.
以上三者的关系通常满足:HORZSIZE = 25.4 * HORZRES/LOGPIXELSX
HORZSIZE为屏幕水平尺寸(定为度量尺寸,以mm计),HORZRES为水平的像素总数(定为像素大小,平时所说的屏幕分辨率,但在这不这么称呼。这里,分辨率定为“每英寸的像素数”),LOGPIXELSX为逻辑像素(假设的每英寸的像素数,并不是刚才所说的实际的“分辨率”)。因此HORZSIZE也称为逻辑宽度。
当我们选择“显示”属性里的大字体时,LOGPIXELSX(通常分为96dpi与120dpi)变大了,这样假设原来的字体为10磅,则原来的字体横向所占像素(实际所占的像素数)为10*(1/72)*LOGPIXELSX,现在LOGPIXELSX变大了,则字体所占像素也大了,因此看起来字体大了。如果HORZRES不变的话,则HORZSIZE应该变小。然后这是和Windows有关的,在16位OS中,HORZSIZE值是固定的。
在XP系统上验证了一下,发现HORZSIZE值与LOGPIXELSX的值也是不变的,如果改变HORZRES的话,则HORZSIZE会发生相应变化,但LOGPIXELSX不变,一直是96。


这些窗口范围表示包含显示器全部宽度和高度的逻辑单位元数值。320毫米宽的屏幕(MM_HIMETRIC下屏幕宽度的逻辑单位为0.01mm,共32000个间隔,故1024像素实际中对应32000*0.01=320mm)也为1260 MM_LOENGLISH单位或12.6英寸(320除以25.4毫米/英寸)。

首先要明确图形只能在设备环境的坐标系中显示。其次要注意视口范围中,视口坐标系的y轴前面的负号表示改变了画笔dc移动时y轴的方向。对于这五种映像方式,视口范围相当于显示屏沿着x轴向屏幕上方折叠后位于实际显示屏上方的矩形区域,想象一下当前显示屏上面放置了一个同样的大小的虚拟显示屏,dc的y值随dc的上升而增加,dc向上移动然后然后绘制图形将显示在虚拟屏幕中,这个事实有一个有趣的结果。此时要想在设备环境的显示区域显示任何东西,必须使用负的y值才能将画笔移动到真实的显示屏上.

void CDemoView::OnPaint()
{
CPaintDC dc(this); // device context for painting
dc.SetMapMode(MM_LOMETRIC);
//dc.TextOut(400,400,"Hello"); //看不到屏幕上的Hello,此时dc画到显示屏上方的虚拟显示屏上了。
dc.TextOut(400,-400,"Hello"); //可以看出屏幕上的Hello
}
为了使自己保持头脑清醒,您可能想避免这样做。
1)一种解决办法是将逻辑的(0,0)点设为显示区域的左下角,您可以通过呼叫SetViewportOrgEx (hdc, 0, cyClient, NULL) ;(假设cyClient是以像素为单位的显示区域的高度)。此时的坐标系是直角坐标系的右上象限。这相当于将视口的原点从设备环境的左上角移动到左下角,将当前显示屏正上方那个虚拟显示屏向下移动,覆盖掉当前的显示屏区域,此时视口的区域和设备环境显示区域重合,所以视口中的物体能显示到计算机屏幕上。例如下面的程序代码:
void CDemoView::OnPaint()
{
CPaintDC dc(this); // device context for painting
CRect Recto;
GetClientRect(&Recto);
dc.SetMapMode(MM_LOMETRIC);
dc.SetViewportOrg(0,Recto.Height());
dc.TextOut(400,400,"Hello"); //结果与题设的结果相同!
}
2)另一种方法是将逻辑(0,0)点设为显示区域的中心:
SetViewportOrgEx (hdc, cxClient / 2, cyClient / 2, NULL) ;代码如下
void CDemoView::OnPaint()
{
CPaintDC dc(this); // device context for painting
CRect Recto;
GetClientRect(&Recto);
dc.SetMapMode(MM_LOMETRIC);
dc.SetViewportOrg(Recto.Width()/2,Recto.Height()/2);
dc.TextOut(400,400,"Hello");
}   现在,我们有了一个真正的4象限笛卡尔坐标系,在x轴和y轴上有相等的按英寸、毫米或twip计算的逻辑单位。
3)您还可以使用SetWindowOrgEx函数来改变逻辑(0,0)点,但是这稍微困难一些,因为SetWindowOrgEx的参数必须使用逻辑单位,先要将(cxClient,cyClient)用DPtoLP函数转换为逻辑坐标。假设变量pt是型态为POINT的结构,下面的代码将逻辑(0,0)点改变到显示区域的中央:
pt.x = cxClient ;
pt.y = cyClient ;
DptoLP (hdc, &pt, 1) ;
SetWindowOrgEx (hdc, -pt.x / 2, -pt.y / 2, NULL) ;代码如下
void CDemoView::OnPaint()
{
CPaintDC dc(this); // device context for painting
CRect Recto;
CPoint pt;
GetClientRect(&Recto);
dc.SetMapMode(MM_LOMETRIC);
pt.x = Recto.Width()/2;
pt.y = Recto.Height()/2;
DPtoLP(dc,&pt,1);
dc.SetWindowOrg(-pt.x,-pt.y);
dc.TextOut(400,400,"Hello"); //结果与2)相同
}


MM_ISOTROPIC和MM_ANISOTROPIC映射模式

1)Isotropic的意思是“同方向性”。 如果想要在使用任意的轴时都保证两个轴上的逻辑单位相同,逻辑坐标系中的圆形在设备环境中现实为圆形,则MM_ISOTROPIC映射方式就是理想的映射方式。这时具有相同逻辑宽度和高度的矩形显示为正方形,具有相同逻辑宽度和高度的椭圆显示为圆。当您刚开始将映射方式设定为MM_ISOTROPIC时,Windows使用与MM_LOMETRIC同样的窗口和视口范围(但是,不要对此有所依赖)。区别在於,您现在可以呼叫SetWindowExt和SetViewportExt来根据自己的偏好改变范围了,然後,Windows将调整范围的值,以便两条轴上的逻辑单位有相同的实际距离。

SetWindowExt(int Lwidth, int Lheight) //参数的单位为逻辑单位(Logical);

SetViewportExt(int Pwidth, int Pheight) //参数的单位为像素(Pixel);

以x轴为例(y轴类似),逻辑坐标系中的x轴的单位刻度=| Pwidth | / | Lwidth |表示x轴上一个逻辑单位等于多少个像素。比如我们先通过GetDeviceCap(LOGPIXELSX)获得在我们的显示器上每英寸等于多少个像素,设为p,然后我们将它赋给Pwidth,将Lwidth赋成2,即Pwidth / Lwidth=p / 2。那么此时逻辑坐标系x轴上的单位刻度就是p / 2个像素;又由于p个像素是代表一个英寸的,所以此时的逻辑坐标系x轴上的单位刻度同时也是半个英寸。还有一点要注意的是,如果Lwidth与Pwidth同号,逻辑坐标的x轴方向与设备坐标系中的x轴方向相同,否则相反。此外,当使用MM_ISOTROPIC模式时,如果通过计算window与viewport范围的比值得到两个方向的单位刻度值不同,那么将会以较小的那个为准。

例如,假设您想要一个「传统的」单象限虚拟坐标系,其中(0,0)在显示区域的左下角,宽度和高度的范围都是从0到2000,并且希望x和y轴的单位具有同样的实际尺寸。以下就是所需的程序:

SetMapMode (hdc, MM_ISOTROPIC) ;

SetWindowExtEx (hdc, 2000, 2000, NULL) ;

SetViewportExtEx (hdc, cxClient, -cyClient, NULL) ;

SetViewportOrgEx (hdc, 0, cyClient, NULL) ;

如果其后用GetWindowExtEx和GetViewportExtEx函数获得了窗口和视端口的范围,可以发现,它们并不是先前指定的值。Windows将根据显示设备的纵横比来调整范围,以便两条轴上的逻辑单位表示相同的实际尺寸。如果显示区域的宽度大于高度(以实际尺寸为准),Windows将调整x的范围,以便逻辑窗口比显示区域视口窄。这样,逻辑窗口将放置在显示区域的左边。代码如下:

void CDemoView::OnPaint()
{
CPaintDC dc(this); // device context for painting
CRect Recto;
GetClientRect(&Recto);
dc.SetMapMode(MM_ISOTROPIC);
dc.SetWindowExt(2000,2000); //现实中的逻辑坐标系的x轴有2000个单位,y轴也有2000个单位。
dc.SetViewportExt(Recto.Width(),-Recto.Height()); // Lwidth与Pwidth异号,所以逻辑坐标系的y轴垂直向上
dc.SetViewportOrg(0,Recto.Height());//将视口原点区域下移到设备环境的左下方,此时窗口中的物体可以显示到设备环境上(显示屏)
dc.Ellipse(-2000,-2000,2000,2000);
dc.MoveTo(0,0);
dc.LineTo(2000,2000);
}
前面给出的程序代码等价的用SetWindowOrg改为:
SetMapMode (MM_ISOTROPIC) ;
SetWindowExtEx (hdc, 2000, 2000, NULL) ;
SetViewportExtEx (hdc, cxClient, -cyClient, NULL) ;
SetWindowOrgEx (hdc, 0, 2000, NULL) ;
在呼叫SetWindowOrgEx中,我们将逻辑点(0, 2000)映像为设备点(0,0)。程序代码如下
void CDemoView::OnPaint()
{
CPaintDC dc(this); // device context for painting
CRect Recto;
GetClientRect(&Recto);
dc.SetMapMode(MM_ISOTROPIC);
dc.SetWindowExt(2000,2000); //现实中的逻辑坐标系的x轴有500个单位,y轴也有500个单位。
dc.SetViewportExt(Recto.Width(),-Recto.Height()); // Lwidth与Pwidth异号,所以逻辑坐标系的y轴垂直向上,与设备环境的y轴相反
dc.SetWindowOrg(0,2000); //将逻辑点(0, 2000)映射为设备点(0,0),逻辑点(0,0)恰好在设备点(0,cyClient)
dc.Ellipse(-2000,-2000,2000,2000);
dc.MoveTo(0,0);
dc.LineTo(2000,2000);
}
您也许想要使用一个四象限的笛卡尔坐标系,四个方向的坐标尺度可以任意指定,(0,0) 必须居于显示区域的中央。如果您想要每条轴的范围从0到1000,则可以使用以下程序代码:
SetMapMode (hdc, MM_ISOTROPIC) ;
SetWindowExtEx (hdc, 1000, 1000, NULL) ;
SetViewportExtEx (hdc, cxClient / 2, -cyClient / 2, NULL) ;
SetViewportOrgEx (hdc, cxClient / 2, cyClient / 2, NULL) ;
void CDemoView::OnPaint()
{
CPaintDC dc(this); // device context for painting
CRect Recto;
GetClientRect(&Recto);
dc.SetMapMode(MM_ISOTROPIC);
dc.SetWindowExt(2000,2000); //现实中的逻辑坐标系的x轴有2000个单位,y轴也有2000个单位。
dc.SetViewportExt(Recto.Width(),-Recto.Height()); // Lwidth与Pwidth异号,所以逻辑坐标系的y轴垂直向上,与设备环境的y轴相反
dc.SetViewportOrg(Recto.Width()/2,Recto.Height()/2);
dc.Ellipse(-1000,-1000,1000,1000);
dc.MoveTo(0,0);
dc.LineTo(2000,2000);
}
在MM_ISOTROPIC映像方式下,可以使逻辑单位大于像素。例如,假设您想要一种映像方式,使点(0,0)显示在屏幕的左上角,y的值向下增长(和MM_TEXT相似),但是逻辑坐标单位为1/16英寸。以下是一种方法:
SetMapMode (hdc, MM_ISOTROPIC) ;
SetWindowExtEx (hdc, 16, 16, NULL) ;
SetViewportExtEx (hdc, GetDeviceCaps (hdc, LOGPIXELSX),
GetDeviceCaps (hdc, LOGPIXELSY), NULL) ;
SetWindowExtEx函数的参数指出了每一英寸中逻辑单位数。SetViewportExtEx函数的参数指出了每一英寸中实际单位数(像素)。
然而,这种方法与Windows NT中的度量映像方式不一致。这些映射方式使用显示器的像素大小和公制大小。要与度量映像方式保持一致,可以这样做:
SetMapMode (hdc, MM_ISOTROPIC) ;
SetWindowExtEx (hdc, 160 * GetDeviceCaps (hdc, HORZSIZE) / 254,
160 * GetDeviceCaps (hdc, VERTSIZE) / 254, NULL) ;
SetViewportExtEx (hdc, GetDeviceCaps (hdc, HORZRES),
GetDeviceCaps (hdc, VERTRES), NULL) ;
在这个程序代码中,视埠范围设定为按像素计算的整个屏幕的大小,窗口范围则必须设定为以1/16英寸为单位的整个屏幕的大小。GetDeviceCaps以HORZRES和VERTRES为参数,传回以毫米为单位的设备尺寸。如果我们使用浮点数,将把毫米数除以25.4,转换为英寸,然后,再乘以16以转换为l/16英寸。但是,由于我们使用的是整数,所以先乘以160,再除以254。
当然,这种坐标系会使逻辑单位大于实际单位。在设备上输出的所有东西都将映像为按1/16英寸增量的坐标值。当然,这样就不能画两条间隔l/32英寸的水平直线,因为这样将需要小数逻辑坐标。
2)在MM_ANISOTROPIC映射方式下,Windows不对您所设定的值进行调整,这就是说,MM_ANISOTROPIC不需要维持正确的纵横比。使用MM_ANISOTROPIC的一种方法是对显示区域使用任意坐标,就像我们对MM_ISOTROPIC所做的一样。下面的程序代码将点(0,0)设定为显示区域的左下角,x轴和y轴都从0到2000:
SetMapMode (hdc, MM_ANISOTROPIC) ;
SetWindowExtEx (hdc, 2000, 2000, NULL) ;
SetViewportExtEx (hdc, cxClient, -cyClient, NULL) ;
SetViewportOrgEx (hdc, 0, cyClient, NULL) ;
在MM_ISOTROPIC方式下,相似的程序代码导致显示区域的一部分在轴的范围之外。但是对于MM_ANISOTROPIC,不论其尺度多大,显示区域的右上角总是(2000, 2000)。如果显示区域不是正方形的,则逻辑x和y的单位具有不同的实际尺度。代码如下:
void CDemoView::OnPaint()
{
CPaintDC dc(this); // device context for painting
CRect Recto;
GetClientRect(&Recto);
dc.SetMapMode(MM_ANISOTROPIC); //和MM_ISOTROPIC进行对比便清楚
dc.SetWindowExt(2000,2000); //现实中的逻辑坐标系的x轴有2000个单位,y轴也有2000个单位。
dc.SetViewportExt(Recto.Width(),-Recto.Height()); // Lwidth与Pwidth异号,所以逻辑坐标系的y轴垂直向上
dc.SetViewportOrg(0,Recto.Height());//将视口原点区域下移到设备环境的左下方,此时窗口中的物体可以显示到设备环境上(显示屏)
dc.Ellipse(-2000,-2000,2000,2000);
dc.MoveTo(0,0);
dc.LineTo(2000,2000);
}
另一种使用MM_ANISOTROPIC的方法是将x和y轴的单位固定,但其值不相等。例如,如果有一个只显示文字的程序,您可能想根据单个字符的高度和宽度设定一种粗刻度的坐标:
SetMapMode (hdc, MM_ANISOTROPIC) ;
SetWindowExtEx (hdc, 1, 1, NULL) ;
SetViewportExtEx (hdc, cxChar, cyChar, NULL) ;
当然,这里假设cxChar和cyChar分别是那种字体的字符宽度和高度。现在,您可以按字符行和列指定坐标。下面的叙述在距离显示区域左边三个字符,上边二个字符处显示文字:
TextOut (hdc, 3, 2, TEXT ("Hello"), 5) ;


MFC缺省地使用MM_TEXT映射模式,一个逻辑单位等于设备中的一个象素。它是实现“所见即所得”的基础。其实大部分情况下使用像素进行工作是很合适的,不需要使用除了MM_TEXT方式外的任何映射方式。但是如果你需要进行类似CAD这样的绘图时,需要以英寸或者厘米尺寸显示图像,就需要使用其他映射方式,以方便编程。因为只要你把映射方式确定后,你只需要在逻辑空间使用逻辑单位绘制图像就好了,无需担心怎样显示在真实的屏幕或打印机上,这些繁琐的工作Windows会做好的。

这时可能会有人问,要是使用cm作为逻辑单位,Windows是如何确定1cm的真实长度呢?我悄悄地告诉你,“Windows 它根本不知道!”,别惊讶,听我接着说,Windows它确实不知道,但是Windows知道像素,这个很重要,它会以像素为依据计算得出1cm的长度(不一定是真实长度)。当程序需要绘制了一个10cm×10cm的矩形时,如果需要显示在打印机上,那么Windows首先通过GetDeviceCaps获取打印机相关信息如每英寸显示320像素(点),而1英寸≈2.54厘米,那么Windows就可以计算出10cm其实就是(320/2.54)*10≈1259.8点。10cm×10cm矩形=1259.8点×1259.8点矩形,打印机上完美的显示了10cm×10cm的矩形没有任何问题,10cm的长度绝对正确,不信你可以用尺子量!

打印机上可以这样做,但是显示器上是不行的,此时Windows给出了一个办法,当然它也是根据人眼的视觉经验,即让显示器每英寸显示96点(正常字体),此时96/2.54≈37.8点,人眼看着很好,但是1cm的长度可能是不真实的。这些不重要,重要的是打印出来绝对正确就好了。

例1. 验证世界窗口的原点和视口的原点位于同一个点,初始时它们与设备坐标系的原点重合均在客户区的左上角。
void CDemoView::OnPaint()
{
CPaintDC dc(this); // 绘图的设备上下文
CRect Recto;
CPen PenBlue;
PenBlue.CreatePen(PS_SOLID, 1, RGB(0, 12, 255));
//dc.SetViewportOrg(100,100);  // Step 1: 仅加上这一句后可以看出,视口原点的下移和客户区的下移
//dc.SetWindowOrg(-100,-100); // Setp 2: 将Step 1注释掉,加上此句可以得到和Step 1相同的结果
dc.SelectObject(&PenBlue);
dc.Ellipse(-100, -100, 100, 100);  //圆心位于屏幕的左上角,仅仅只有圆的四分之一部分(270度到360度的部分)显示在屏幕上。
dc.MoveTo(0,0);
dc.LineTo(100,100);
CPen PenBlack;
PenBlack.CreatePen(PS_SOLID, 1, BLACK_PEN);
dc.SelectObject(&PenBlack);
GetClientRect(&Recto); // 得到客户区域的尺寸,我以前没看出客户区的左上角就是视口的原点,而误以为是设备坐标的原点了。
dc.MoveTo(Recto.Width() / 2, 0); //dc的移动是参考视口原点画笔的相对移动,注意这里视口的原点(也是窗口原点)并没有移动
dc.LineTo(Recto.Width() / 2, Recto.Height());
dc.MoveTo(0, Recto.Height() / 2);
dc.LineTo(Recto.Width(), Recto.Height() / 2);
}
例2. 验证世界窗口的原点和视口的原点位于同一个点,默认MM_TEXT映射模式下x轴水平向右,y轴垂直向下。其他固定映射模式的使用
void CDemoView::OnPaint()
{
CPaintDC dc(this); // device context for painting
dc.SetMapMode(MM_TEXT); //读者可以将MM_TEXT换为MM_LOMETRIC等其他的固定映射模式
dc.SetViewportOrg(380, 220);
// Use a red pen
CPen PenRed(PS_SOLID, 1, RGB(255, 0, 0));
dc.SelectObject(PenRed);
// A circle whose center is at the origin (0, 0)
dc.Ellipse(-100, -100, 100, 100);
// Use a blue pen
CPen PenBlue(PS_SOLID, 1, RGB(0, 0, 255));
dc.SelectObject(PenBlue);
// Horizontal axis
dc.MoveTo(-380, 0);
dc.LineTo(380, 0);
// Vertical axis
dc.MoveTo(0, -220);
dc.LineTo(0, 220);
// An orange pen
CPen PenOrange(PS_SOLID, 1, RGB(255, 128, 0));
dc.SelectObject(PenOrange);
// A diagonal line at 45 degrees
dc.MoveTo(0, 0);
dc.LineTo(120, 120); //直线没有在笛卡尔坐标系的45度位置,而是位于笛卡尔坐标系统的第四象限
}
例3. 自定义映射模式MM_ISOTROPIC的使用
void CDemoView::OnPaint()
{
CPaintDC dc(this); // device context for painting
dc.SetMapMode(MM_ISOTROPIC);
dc.SetWindowExt(10240,7680);
dc.SetViewportExt(1024,768);
dc.Ellipse(-100,-100,100,100); //以客户区左上角(0,0)画出一个半径为10pixels的圆
}其本质就是,X方向,每个逻辑单位有1024/10240个象素,Y方向每个逻辑单位有768/7680个象素。因此以下代码有相同的作用。
void CDemoView::OnPaint()
{
CPaintDC dc(this); // device context for painting
dc.SetMapMode(MM_ISOTROPIC);
dc.SetWindowExt(102400,76800);
dc.SetViewportExt(10240,7680);
dc.Ellipse(-100,-100,100,100);
}
例4. 自定义映射模式MM_ANISOTROPIC的使用以左上角为原点,X轴和Y轴为1000的坐标
void CDemoView::OnDraw(CDC* pDC)
{
CDemoDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: add draw code for native data here
CRect rect;
GetClientRect(&rect);
pDC->SetMapMode(MM_ANISOTROPIC);
//pDC->SetViewportOrg(0,0); //可以注释掉
pDC->SetViewportExt(rect.right,rect.bottom);
pDC->SetWindowOrg(0,0);
pDC->SetWindowExt(1000,1000);

pDC->MoveTo(50,50);
pDC->LineTo(50,950);
pDC->LineTo(950,950);
pDC->LineTo(50,50);
}
代码分析:
1. GetClientRect(&rect); 取得客户区矩形区域,将其存放在rect中
2. 用pDC->SetMapMode(MM_ANISOTROPIC); 设置映射模式
3. 通过pDC->SetViewportOrg(0,0);设置逻辑坐标的原点。
4. 通过pDC->SetViewportExt(rect.right,rect.bottom);和
pDC->SetWindowExt(1000,1000);来确定逻辑坐标下和设备坐标下的尺寸对应关系
5. 在MM_ANISOTROPIC模式下,X轴单位和Y轴单位可以不相同
6. 坐标方向的确定方法是如果逻辑窗范围和视口范围符号相同,则逻辑坐标的方向和视口的方向相同,即X轴向右为正,Y轴向下为正。
7. 如果将显示模式改为MM_ISOTROPIC,那么X轴单位和Y轴单位一定相同,感兴趣的读者可以自己使一下。
例5.绘制点状网格
void CDemoView::OnPaint()
{
CPaintDC dc(this); // device context for painting
CRect Recto;
GetClientRect(&Recto);
CBrush bgBrush(BLACK_BRUSH);

dc.SelectObject(bgBrush);
dc.Rectangle(Recto);
for(int x = 0; x < Recto.Width(); x += 20)
{
for(int y = 0; y < Recto.Height(); y += 20)
{
dc.SetPixel(x, y, RGB(255, 255, 255));
}
}
}注意屏幕闪的厉害时,可以添加如下代码
BOOL CDemoView::OnEraseBkgnd(CDC* pDC)
{
return TRUE;
}
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  windows gdi