Chapter 5 Basic Drawing

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:

当你在屏幕上或者打印机上画图就需要一个hdc,Windows给你一个hdc相当于允许你使用那个设备了,把hdc作为参数传给GDI函数,GDI函数就知道你要在哪个设备上绘图了。The device context contains many "attributes" that determine how the GDI functions work on the device. DC中有许多决定GDI怎么工作的属性,例如TextOut这一个GDI函数就是DC里面有字符的字体,颜色,文体底色,字符间距等等属性都默认好了,所以GDI函数才知道怎么去画图,而且也是由于这点TextOut函数参数变少了,不需要指定字体,颜色什么的了。

获取DC的方法:(1)BeginPaint,只在PAINTSTRUCT结构体rcpaint矩形规定的无效区域绘图(2)GetDC,在整个Client区域绘图(3)GetWIndowDC,在整个Window上绘图,包括标题栏,工具栏,状态栏之类(4)CreateDC(5)有时只需要获取DC中的信息而不用画图就调用CreateIC.

获取DC相对应的物理设备的性能:GetDeviceCaps(hdc,index);   index是WINGDI.h中预先定义好的宏
For example, the iIndex value of HORZRES causes GetDeviceCaps to return the width of the device in pixels; a VERTRES argument returns the
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:

首先看完这部分才知道我以前一直认为右键桌面设置的分辨率就是分辨率是错的。
分辨率:
In this book I'm going to use the word "resolution" in the strict sense of a number of pixels per metrical unit, generally an inch.
分辨率指的是每一个度量单位中像素的个数,一般是每英寸中像素数目。
像素规模与度量规模:
pixel size or pixel dimension指的是水平或者竖直像素的总数,metrical size or metrical dimension指的是device in inches or millimeters
640*480   800*600 这些常见的是像规模,水平像素总数*竖直像素总数
字体:
控制面板中有小字体与大字体选,字体的大小是用point size来表示的,也叫做磅。其中1 point=1/72 inch
TEXTMETRIC结构体中tmHeight=font size(真正字符最大高度)+tmInteralLeading(字符重读音符等)
小字体分辨率为96pixels/inch    10point字体=10/72inch=10/72*96pixels=13pixels
 
显示器物理大小计算:
HORZSIZE(mm)=25.4*(HORZRES/LOGPIXELSX)
 TEXT ("HORZSIZE"), TEXT ("Width in millimeters:")
TEXT ("HORZRES"), TEXT ("Width in pixels:")
TEXT ("LOGPIXELSX"), TEXT ("Horizontal dots per inch:")

 

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) ;

SelectClipRgn(hdc,hRgn);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值