GDI:
GDI(Graphics Device Interface)本质上是上百条函数,各种数据结构,结构体,宏等
GDI consists of several hundred function calls and some associated data types, macros, and structures.
不同的显示器,打印机这些输出设备所装的驱动都不同,例如显示器的驱动是直接和硬件打交道,而打印机的驱动能把GDI的命令转化为打印机懂的命令,不同牌子的显示器驱动也有可能不同,例如都是画一条直线,不同显示器画法可能不同,程序想画一条直线没可能是直接调用这些设备的驱动吧,调用A设备的驱动画了一条直线,同样的代码移植到B设备,因为驱动不同而出错。所以加上了GDI这一层,把绘图与硬件分隔开。就是你用GDI里面的函数来编写代码(例如画一条直线),同样的代码无论在A还是B设备,GDI都会帮你搞掂和硬件的交流,至于怎么搞掂的过程就不用理了。举个类似的例子就是C语言,无论你在16位机还是在32位机,硬件有所不同的情况下,你C语言写出来的代码都是一样的,都是从逻辑层面出发而不需顾忌到硬件的问题,GDI同样也是这样的功能。
GDI中函数的分类:
(1)获取与释放DC的函数 (2)获取DC内部信息的函数,例如GetTextMetrics (3)直接绘图的函数,例如TextOut (4)修改DC属性的函数,例如SetTextAlign,SetTextColor等等 (5)处理DC中objects的函数,例如处理DC中画笔,画刷等
Primitives:
The types of graphics you display on the screen or the printer can themselves be divided into several categories, which are called "primitives."
(1)直线,曲线,弧等(2)可填充区域(3)位图(4)文本
一些其他的东西:
(1)映射模式(mapping modes),Windows默认的映射模式是MM_TEXT,即是逻辑坐标是以像素为单位的,你可以设置这个映射模式,以英寸为单位也行,几分之一英寸为单位也行,随你自己设
(2)图元文件(Metafiles)保存一些GDI命令(以二进制的形式保存)
(3)裁剪(clipping)只能在裁剪区域绘图
DC:
height of the device in pixels. If hdc is a handle to a screen device context, that's the same information you can
get from GetSystemMetrics . If hdc is a handle to a printer device context, GetDeviceCaps returns the height and
width of the printer display area in pixels.
The size of Device:
分辨率:
字体:
显示器物理大小计算:
Draw Dots and Lines:
SetPixel(hdc,x,y,crColor);
GetPixel(hdc,x,y);
DC中Current Position的参数:默认(0,0) 可以改变其值的函数:MoveToEx,LineTo,PolyLineTo,PolyBezierTo 获取其值的函数:GetCurrentPositionEx
MoveToEx (hdc, xBeg, yBeg, NULL) ;//第四个参数是POINT的指针,用来存放之前的Current Position
LineTo (hdc, xEnd, yEnd) ;
GetCurrentPositionEx (hdc, &pt) ;//&pt是POINT结构体的指针
Polyline (hdc, apt, iCount) ;
PolylineTo(hdc,apt,iCount);用Current Position作为起点
Rectangle (hdc, xLeft, yTop, xRight, yBottom) ;//会填充
Ellipse (hdc, xLeft, yTop, xRight, yBottom) ;//会填充
RoundRect (hdc, xLeft, yTop, xRight, yBottom,xCornerEllipse, yCornerEllipse) ;//xCornerEllipse与yCornerEllipse是小椭圆的宽与高
//以下3个画弧线都是按逆时针方向从起点画到终点,起点为(xStart,yStart)与椭圆圆心连线与圆弧交点,终点类似
Arc (hdc, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd, yEnd) ;//画弧
Chord (hdc, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd, yEnd) ;//画弦,会填充
Pie (hdc, xLeft, yTop, xRight, yBottom, xStart, yStart, xEnd, yEnd) ;//画扇形,会填充
PolyBezier(hdc,apt,iCount);//apt中POINT结构体顺序:起始点,第一个控点,第二个控点,终点,第二条线第一个控点。。。
PolyBezierTo(hdc,apt,iCOunt);//用到Current Position
Pen:
Stock Pen:WHITE_PEN,BLACK_PEN,NULL_PEN
GetStockObject(int index); SelectObject(hdc,hObj);
创建Pen有CreatePen(iPenStyle, iWidth, crColor);//iPenStyle:PS_SOLID PS_DASH PS_DOT PS_DASHDOT PS_DASHDOTDOT PS_NULL PS_INSIDEFRAME,对于虚线与点线如果宽度大于1会自动变实线的。虚线与点线之间的空隙用background Color与background Mode共同决定。
CreatePenIndirect(&logpen);
LOGPEN结构体有lopnStyle,lopnWidth(POINT结构体),lopnColor三项
自己创建的Pen最后必须要Delete,被选进DC中的Pen与Stock Pen不能delete
一般在WM_CREATE创建需要的画笔,在WM_DESTORY里面DELETE画笔,或者想在WM_PAINT里面创建,那应该在EndPaint之后delete
已知hpen,通过GetObject(hpen,sizeof(LOGPEN),(LPVOID)&logpen);来获取hpen的信息。
GetCurrentObject(hdc,OBJ_PEN);获取DC中的画笔。
SetBKColor(hdc,rcColor); GetBKColor
SetBKMode(hdc,TRANSPARENT(透明)/OPAQUE(不透明)); GetBKMode
Drawing Mode:DC中的属性,线条除了与Pen有关还与目标像素的颜色有关,总体颜色取决于Pen与目标像素颜色进行按位布尔运算的结果,这种2元布尔运算叫做ROP2,DC默认ROP2为R2_COPYPEN就是直接把pen的颜色赋给目标像素,此外还有其他mode,例如R2_BACLK不管pen与目标像素什么色线条都黑色,R2_WHITE不管怎样都是白色,R2_NOP=D就是线条的色就是目标像素的色,R2_NOTCOPYPEN就是~p与画笔颜色相反的色。
Brush:
Stock brush:WHITE_BRUSH,BLACK_BRUSH,LTGRAY_BRUSH,GRAY_BRUSH,DKGRAY_BRUSH,NULL_BRUSH
画填充区域的GDI函数有:Rectangle,Ellipse,CHord,Pie,RoundRect,Polygon,PolyPolygon//outline用DC的PEN画,里面用DC的Brush填充
Polygon(hdc,apt,iCount);//与Polyline类似,不过如果最后一个POINT不等于一开始的POINT,Windows会追加一条线连回去。
PolyPolygon(hdc,apt,aiCount,iPolyCount);//iPolyCount给出了polygon的数目,aiCount数组给出了每个Polygon的数组的点数,apt给出所有的点.
Poly fill Mode(DC属性);SetPolyFillMode(hdc,ALTERNATE/WINDING)
CreateSolidBrush(crColor);
CreateHatchBrush(iHatchStyle,crColor);//阴影之间的空隙与background Color与backGround Mode相关
CreateBrushIndirect(&logbrush);
LOGBRUSH结构体:lbStyle,lbColor,lbhatch
GetObject(hbrush,sizeof(LOGBRUSH),(LPVOID)&logbrush);
GetCurrentObject(hdc,OBJ_BRUSH);
MapMode:
分逻辑坐标系统与设备坐标系统,逻辑坐标系统就是你GDI函数位置参数所属于的那个假想的坐标系统,而设备坐标系统就是应用于计算机的坐标系统,有3种:
(1)用Dispaly参数的CreateDC得到的DC的设备坐标系统左上角(0,0)位于屏幕的左上方;(2)用GetWindowDC得到的DC的设备坐标系统左上角(0,0)位于窗口的左上角;(3)GetDC和BeginPaint得到的DC的设备坐标系统左上角(0,0)位于Client的左上方;
有一点要记住的就是:后面set Org和Ext的时候set Windows的就用逻辑坐标系统,set Viewport的就用设备坐标系统,设备坐标系统方向永远不变(x→,y↓),这点决定了xy轴方向的问题
映射模式换算方式:
xViewExt
xViewport = (xWindow - xWinOrg) × ________ + xViewOrg
xWinExt
yViewExt
yViewport = (yWindow - yWinOrg) × ________ + yViewOrg
yWinExt
DC中有4个关于这个变换的属性WindowOrg,ViewportOrg,WIndowExt和ViewportExt,单从公式可以看出映射的本质就是把一个逻辑坐标到WindowOrg的距离按照比例缩放到设备坐标距离ViewportOrg的位置上。
Windows提供了好几种比例:1=1像素:MM_TEXT 1=0.1mm:MM_LOMETRIC 1=0.01mm:HIMETRIC 1=0.01inch:LOENGLISH 1=0.001inch:HIENGLISH
(ps:除了MM_TEXT是x轴→,y轴↓方向递增,其他都是x轴→,y轴↑递增,也就是TextOut(hdc,0,-100,text,len);这样表示文本在离顶部100逻辑单位下方)
同时这些提供好的由于比例固定了,所以决定比例的Ext参数不能修改,但是Org参数能修改,举个例子:
SetMapMode(hdc,MM_TEXT);
SetViewportOrgEx(hdc,100,100,NULL);//这时TextOut(0,0)就会在100,100那里输出
SetWindowOrgEx(hdc,100,100,NULL);//这时TextOut(0,0)又会变回0,0那里输出
至于:
SetMapMode(hdc,MM_LOMETRIC);//不想TextOut()里面用负数的办法
SetViewportOrgEx(hdc,0,cyClient,NULL);//那么想在输出的TextOut(0,100)就会变成靠着左边界,离底部100个逻辑单位的地方
也可以设SetWindowOrgEx,把WindowOrg设为(0,-cyClient),不过要把这个点DptoLP(hdc,&pt,iCountpt);
Windows还有可以让你自己设比例的mapmode:MM_ISOTROPIC(x方向比例与y方向比例一样,不一样Windows也会调整) MM_ANISOTROPIC(x与y方向比例可不一致)
你不设Ext改变比例默认以MM_LOMETRIC的比例,所以最好修改下,一般修改方式是SetWindowExtEx(逻辑坐标的size);SetViewportExtEx(cxClient,cyClient);
例如SetMapMode(hdc,MM_ISOTROPIC);
SetWindowExtEx(hdc,10,10,NULL);
SetViewportExtEx(hdc,cxClient,cyClient,NULL);//这样之后如果窗口高度小于宽,那么比例就是1=1/10cxClient了
可以Rectangle(hdc,1,1,3,3);看下效果
至于MM_ANISOTROPIC这种mapmode下Windows就不会自动帮你调整,Ext的比例按你自己设定的。
例如SetMapMode(hdc,MM_ANISOTROPIC);
SetWindowExtEx(hdc,1,1,NULL);
SetViewportExtEx(hdc,cxChar,cyChar,NULL);
那么之后逻辑坐标系统中x轴的1个单位就是1个cxChar,y轴的1个单位就是1个cyChar,这种应用于等宽字符特别好用:SelectObject(hdc,GetStockObject(SYSTEM_FIXED_FONT));
另外MM_ANISOTROPIC还有一个特点就是它会继承上一次mapmode的Ext。
Rectangle,Region and Clipping:
FillRect(hdc,&rect,brush);//用brush填充rect
FrameRect(hdc,&rect,brush);//用brush画rect的边界
InvertRect(hdc,&rect);//把rect里面的像素全部取反,白色会变成黑色
SetRect(&rect,left,top,right,bottom);
OffsetRect(&rect,x,y);//x轴移动x个单位,y轴移动y个单位
InflateRect(&rect,x,y);//x轴左右均增加x个单位,y轴同理
SetRectEmpty(&rect);
CopyRect(&DesRect,&SrcRect);
IntersectRect(&DesRect,&SrcRect1,&SrcRect2);//计算矩形交集
UnionRect(&DestRect,&SrcRect1,&SrcRect2);//计算矩形的并集
bEmpty = IsRectEmpty (&rect) ;
bInRect = PtInRect (&rect, point) ;
PeekMessage(&msg,NULL,0,0,PM_REMOVE);//PeekMessage与GetMessage前4个参数一样,最后一个参数:如果要删除消息就PM_REMOVE,不删除就PM_NOREMOVE.
PeekMessage与GetMessage不同的是PeekMessage在消息队列中看下有没消息,有消息就返回true,没消息就返回false,而GetMessage如果没消息就一直等,等到有为止.
消息循环:
while (GetMessage (&msg, NULL, 0, 0))
{
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
return msg.wParam ;
用PeekMessage代替:
while (TRUE)
{
if (PeekMessage (&msg, NULL, 0, 0, PM_REMOVE))
{
if (msg.message == WM_QUIT)
break ;
TranslateMessage (&msg) ;
DispatchMessage (&msg) ;
}
else
{
[other program lines to do some work]
}
}
return msg.wParam ;
至于为什么要特别处理WM_QUIT:因为有消息PeekMessage就会返回true,不像GetMessage遇到WM_QUIT就返回0,所以要特别处理下。
这里注意的是WM_PAINT对于GetMessage和PeekMessage都是不能删除的,因为只要有无效区域就会有WM_PAINT,要真正删除WM_PAINT只有有效化去无效区域才行,所以企图通过While(PeekMessage(&msg,NULL,0,0,PM_REMOVE));来清空消息队里会因为遇到WM_PAINT而一直死循环。
Region与Pen,Brush一样是GDI的Object,创建自己的Region则记住要DeleteObject,它被选进DC就会作为Clipping区域(只能在这个区域作图)
HRGN hRgn=CreateRectRgn(left,top,right,bottom);
hRgn = CreateRectRgnIndirect (&rect) ;
hRgn = CreateEllipticRgn (xLeft, yTop, xRight, yBottom) ;
hRgn = CreateEllipticRgnIndirect (&rect) ;
hRgn = CreatePolygonRgn (&point, iCount, iPolyFillMode) ;
iRgnType = CombineRgn (hDestRgn, hSrcRgn1, hSrcRgn2, iCombine) ;//hDestRgn一定要初始化过才能用
FillRgn (hdc, hRgn, hBrush) ;
FrameRgn (hdc, hRgn, hBrush, xFrame, yFrame) ;
InvertRgn (hdc, hRgn) ;
PaintRgn (hdc, hRgn) ;