x=(xEnd <<8)|xstart下载; 什么作用

见招拆招 Windows 程序设计 (四)
见招拆招 Windows 程序设计 (四)
相关的例子:
作者:Zoologist&nbsp于上传&
图形设计,算是“不正经”的命题吧?初中我和师傅学习信息学竞赛的时候就热衷于这些,如何绘制漂亮的图形,如何实现简单的动画效果。那时候用的是Pascal语言,资料也非常少基本上只能自己摸索。后来偶然得到一本《Pascal大全》高兴得不得了。
不过自从操作系统从DOS进化到Windows,图形是用户交互的最好手段,图形方面的知识也成为编程者必须掌握的知识----对于大多数,“正常”的用户来说,在控制台方式下输入命令完成各种操作远比点击执行操作复杂的多。
这一期的内容就是在介绍图形方面的Windows编程,只是一点皮毛而已。其中的一些描述部分,我仍然保持C语言,因为如果用汇编语言描述可能会写得很长让人忘记了程序段的目的,不过整个的程序仍然是使用汇编语言进行描述的。
图形设备接口(GDI:Graphics Device
Interface)是Windows的子系统,它负责在显示器和打印机上显示图形。正如您所认为的那样,GDI是Windows非常重要的部分。不只您为Windows编写的应用系统在显示视觉信息时使用GDI,就连Windows本身也使用GDI来显示使用者接口对象,诸如菜单、滚动条、图标和鼠标光标。
不幸的是,如果要对GDI进行全面的讲述,将需要一整本书----当然更不是几期简单的文章。(推荐《Windows图形编程》,虽然俺没有看过,不过从各方面得到的信息来看是一本好书)在本章中,我只是想向您提供画线和填入区域的基本知识,这对于理解下面几章的GDI已经足够了。在后面几期中会讲述GDI支持的位图、metafile以及格式化文字。
GDI 的结构
从程序写作者的观点来看,GDI由几百个函数呼叫和一些相关的数据结构、宏和结构组成。但是在开始讲述这些函数的细节之前,让我们先从宏观上了解一下GDI的整体结构。
Windows 98和Microsoft Windows NT/XP中的图形主要由GDI32.DLL动态链接库输出的函数来处理。在Windows
98中,这个GDI32.DLL实际是利用16位GDI.EXE动态链接库来执行许多函数。在Windows NT/XP中,GDI.EXE只用于16位的程序。
这些动态链接库调用您安装的显卡和打印机呼驱动程序中的例程。显卡驱动程序存取视频显示器的硬件,打印机驱动程序将GDI命令转换为各种打印机能够理解的代码或者命令。显然,不同的显卡和打印机要求不同的设备驱动程序。
因为PC兼容机种上可以连接许多种不同的视频设备,所以,GDI的主要目的之一是支持与设备无关的图形。Windows程序应该能够毫无困难地在Windows支持的任意一种图形输出设备上执行,GDI通过将您的程序和不同输出设备的特性隔离开来的方法来达到这一目的。
图形输出设备分为两大类:光栅设备和矢量设备。大多数PC的输出设备是光栅设备,这意味着它们以图点构成的数组来表示图像,这类设备包括视频显示卡、点阵打印机和激光打印机。矢量设备使用线来绘制图像,通常局限于绘图机。
(光栅好比用石头在沙地上拼字,比如,很早之前使用的UCDOS,里面包含 16X16 点阵字库,存放的就是每一个字的点的信息;而矢量存放得更像是方法,比如,人字的写法就是“一撇一捺”。Windows中有很多“矢量字库”,放大很多倍后仍然不会失真)
许多传统的计算机图形程序设计方式都是完全以矢量为主的,这意味着使用矢量图形系统的程序与硬件有着一定层次的隔离。输出设备用像素表示图形,但是程序与程序接口之间并不是用像素进行沟通的。您当然可以使用Windows
GDI作为一个高阶的矢量绘制系统,同时也可以将它用于比较低阶的像素操作。从这方面来看,Windows GDI和传统的图形接口语言之间的关系,就如同C和其它程序设计语言之间的关系一样。C以它在不同操作系统和环境之间的高度可移植性而闻名,然而C也以允许程序写作者进行
底层系统呼叫而闻名,这些呼叫在其它高级语言中通常是不可能的。正如C有时被认为是一种「高级汇编语言」一样,您可以认为GDI是图形设备硬件之间的一种高阶界面。
您已经看到,Windows内定使用像素坐标系统。大多数传统的图形语言使用「虚拟」坐标系,其水平和垂直轴的范围在0到32,767之间。虽然有些图形语言不让您使用像素坐标,但是Windows
GDI允许您使用两种坐标系统之一(甚至依据实际度量衡的坐标系)。您可以使用虚拟坐标系以便让程序独立于硬件之外,或者也可以使用设备坐标系而完全迎合硬设备提供的环境。
某些程序写作者认为一旦开始使用操作像素的程序设计方式,就放弃了设备无关性。我们在上一期看到,这不完全是正确的,其中的诀窍是在与设备无关的方式中使用像素。这要求图形接口语言为程序提供
一些方法来确定设备的硬件特征,并进行适当的调节。例如,在SYSMETS程序中,我们根据标准系统字体字符的像素大小来确定屏幕上的文字间距,这种方法允许程序针对分辨率、文字大小和方向比例各不相同的显示卡
进行相应的调节。您将在本章看到一些用于确定显示尺寸的其它方法。
早期,许多使用者在单色显示器上执行Windows。为此,GDI的设计保证了您可以在编写一个程序时不必太担心色彩问题-也就是说,Windows可以将色彩转换为灰阶显示。甚至在今天,Windows
98/NT/XP使用的视频显示已经具有了不同的色彩能力(16色、256色、「high-Color」以及「true-color」)。虽然,彩色喷墨打印机的成本已经很低了,但是大多数使用者仍然坚持使用黑白打印机。盲目地使用这些设备是可以的,但是您的程序也应该能决定在某种显示设备上有多少色彩可以使用,从而最佳利用硬件功能。
当然,就如同您编写C程序时,为了使它在其它计算机上执行而遇到一些微妙的移植性问题一样,您也可能不小心让设备依赖性溜进您的Windows程序,这就是不与硬件完全隔离的代价。您还应该知道Windows
GDI的局限。虽然可以在显示器上到处移动图形对象,但GDI通常是一个静态的显示系统,只有有限的动画支持。如果需要为游戏编写复杂的动画,就应该研究一下Microsoft
DirectX或者OpenGL,它提供了您需要的支持。
GDI函数呼叫
组成GDI的几百个函数呼叫可以分为几大类:
取得(或者建立)和释放(或者清除)设备内容的函数
我们在前面的章节中已经看到过,您在绘图时需要设备内容句柄。GetDC和RealseDC函数让您在非WM_PAINT的消息处理期间来做到这一点,而BeginPaint和EndPaint函数(虽然在技术上它们是USER模块而不是GDI模块的一部分)在进行绘图的WM_PAINT消息处理期间使用。我们马上还会介绍有关设备内容的其它一些函数。
取得有关设备内容信息的函数 再以SYSMETS程序为例,我们使用GetTextMetrics函数来取得有关设备内容中目前所选字体的尺寸信息。在本章后面,我们将看到一个取得非常广泛的设备内容信息的DEVCAPS1程序。
绘图函数 显然,在所有前提条件都得以满足之后,这些函数是真正重要的部分。在上一期中,我们使用TextOut函数在窗口的显示区域显示一些文字。我们将看到,其它GDI函数还可以让您画线、填入区域。
设定和取得设备内容参数的函数 设备内容的「属性」决定有关绘图函数如何工作的细节。例如,用SetTextColor来指定TextOut(或者其它文字输出函数)所绘制的文字色彩。在SYSMETS程序中,我们使用SetTextAlign来告诉GDI:TextOut函数中的字符串的开始位置应该在字符串的右边而不是内定的左边。设备内容的所有属性都有默认值,取得设备内容时这些默认值就设定好了。对于所有的Set函数,都有相应的Get函数,以允许您取得目前设备内容属性。
使用GDI对象的函数 GDI在这里变得有点混乱。首先举一个例子:内定时使用GDI绘制的所有直线都是实线并具有一个标准的宽度。您可能希望绘制更细的直线,或者是由一系列的点或短划线组成的直线。这种线的宽度和这种线的画笔样式不是设备内容的属性,而是一个「逻辑画笔」的特征。您可以通过在CreatePen、
CreatePenIndirect或ExtCreatePen函数中指定这些特征来建立一个逻辑画笔,这些函数传回一个逻辑画笔的句柄(虽然这些函数被认为是GDI的一部分,但是和大多数GDI函数呼叫不一样,它们不要求设备内容的句柄)。要使用这个画笔,就要将画笔句柄选进设备内容。我们认为,设备内容中目前选中的画笔就是设备内容的一个属性。这样,您画任何线都使用这个画笔,然后,您可以取消设备内容中的画笔选择,并清除画笔对象。清除画笔对象是必要的,因为画笔定义占用了分配的内存空间。除了画笔以外,GDI对象还用于建立填入封闭区域的画刷、字体、位图以及GDI的其它一些方面。
GDI基本图形
您在屏幕或打印机上显示的图形型态本身可以被分为几类,通常被称为「基本图形」,它们是:
直线和曲线 线条是所有矢量图形绘制系统的基础。GDI支持直线、矩形、椭圆(包括椭圆的子集,也就是我们所说的「圆」)、椭圆圆周上的部分曲线即所谓的「弧」以及贝塞尔曲线(Bezier
spline),我们将在本期中分别对它们进行介绍。所有更复杂的曲线可由折线(polyline)代替,折线通过一组非常短的直线来定义一条曲线。线条用设备内容中选中的目前画笔绘制。
填入区域 当一系列直线或者曲线封闭了一个区域时,该区域可以使用目前GDI画刷对象进行填图。这个画刷可以是实心色彩、图案(可以是一系列的水平、垂直或者对角标记)或者是在区域内垂直或者水平重复的位图图像。
位图 位图是位的矩形数组,这些位对应于显示设备上的像素,它们是光栅图形的基础工具。位图通常用于在视频显示器或者打印机上显示复杂(一般都是真实的)图像。位图还可以用于显示必须快速绘制的小图像(诸如图标、鼠标光标以及在应用工具条中出现的按钮等)。GDI支持两种型态的位图-旧式的(虽然还非常有用)「设备相关」位图,是GDI对象;和新的「设备无关」位图(或者DIB),可以储存在磁盘文件中。后面几期我们会讨论位图。
文字 文字的数学味道不像计算机图形的其它方面那样浓。文字和几百年的传统印刷术有关,它被许多印刷工人看作为一门艺术。因此,文字通常不仅是所有的计算机图形系统中最复杂的部分,而且(如果识字还是社会基本要求的话)也是最重要的部分。用于定义GDI字体对象和取得字体信息的数据结构是Windows中最庞大的部分之一。从Windows
3.1开始,GDI开始支持TrueType字体,该字体是在填入轮廓线基础上建立的,这样的填入轮廓线可由其它GDI函数处理。依据兼容性和储存大小的考虑,我们会在后面的一期讨论这个话题。
GDI的其它部分无法这么容易地分类,它们是:
映像模式和变换 虽然内定以像素为单位进行绘图,但是您并非局限于此。GDI映像模式允许您以英寸(或者甚至以几分之一英寸)、毫米或者任何您想使用的单位来绘图。
Metafile Metafile是以二进制形式储存的GDI命令集合。Metafile主要用于通过剪贴板传输矢量图形。
绘图区域 绘图区域是形状任意的复杂区域,通常定义为较简单的绘图区域组合。在GDI内部,绘图区域除了储存为最初用来定义绘图区域的线条组合以外,还以一系列扫描线的形式储存。您可以将绘图区域用于绘制轮廓、填入图形和剪裁。
路径 路径是GDI内部储存的直线和曲线的集合。路径可以用于绘图、填入图形和剪裁,还可以转换为绘图区域。
剪裁 绘图可以限制在显示区域的某一部分中,这就是所谓的剪裁。剪裁区域是不是矩形都可以,剪裁通常是通过区域或者路径来定义的。
调色盘 自订调色盘通常限于显示256色的显示器。Windows仅保留这些色彩之中的20种以供系统使用,您可以改变其它236种色彩,以准确显示按位图形式储存的真实图像。
打印 虽然本章限于讨论视频显示,但是您在本章中所学到的全部知识都适用于打印。
在开始绘图之前,让我们比上一期更精确地讨论一下设备内容。&&&
当您想在一个图形输出设备(诸如屏幕或者打印机)上绘图时,您首先必须获得一个设备内容(或者DC)的句柄。将句柄传回给程序时,Windows就给了您使用设备的权限。然后您在GDI函数中将这个句柄作为一个参数,向Windows标识您想在其上进行绘图的设备。
设备内容中包含许多确定GDI函数如何在设备上工作的目前「属性」,这些属性允许传递给GDI函数的参数只包含起始坐标或者尺寸信息,而不必包含Windows在设备上显示对象时需要的所有其它信息。例如,呼叫TextOut时,您只需要在函数中给出设备内容句柄、起始坐标、文字和文字的长度。您不必指定字体、文字颜色、文字后面的背景色彩以及字符间距,因为这些属性都是设备内容的一部分。当您想改变这些属性之一时,您呼叫一个可以改变设备内容中属性的函数,以后针对该设备内容的TextOut呼叫来使用改变后的属性。
取得设备内容句柄
Windows提供了几种取得设备内容句柄的方法。如果在处理一个消息时取得了设备内容句柄,应该在退出窗口函数之前释放它(或者删除它)。一旦释放了句柄,它就不再有效了。对于打印机设备内容句柄,规则就没有这么严格。&&&
最常用的取得并释放设备内容句柄的方法是,在处理WM_PAINT消息时,使用BeginPaint和EndPaint呼叫:invoke BeginPaint,hwnd,addr psmov hdc,eax
其它行程序invoke EndPaint,hwnd,addr ps
变量ps是型态为PAINTSTRUCT的结构,该结构的hdc字段是BeginPaint传回的设备内容句柄。
PAINTSTRUCT结构又包含一个名为rcPaint的RECT(矩形)结构,rcPaint定义一个包围窗口显示区域无效范围的矩形。使用从BeginPaint获得的设备内容句柄,只能在这个区域内绘图。BeginPaint呼叫使该区域有效。
Windows程序还可以在处理非WM_PAINT消息时取得设备内容句柄:
invoke GetDC,EAXmov hdc,EAX
其它行程序invoke ReleaseDC,hMine,hdc
这个设备内容适用于窗口句柄为hwnd的显示区域。这些呼叫与BeginPaint和EndPaint的组合之间的基本区别是,利用从GetDC传回的句柄可以在整个显示区域上绘图。当然,
GetDC和ReleaseDC不使显示区域中任何可能的无效区域变成有效。
Windows程序还可以取得适用于整个窗口(而不仅限于窗口的显示区域)的设备内容句柄:
invoke GetWindowDC, hwndmov eax,hdc其它行程序invoke ReleaseDC, hwnd
这个设备内容除了显示区域之外,还包括窗口的标题列、菜单、滚动条和框架(frame)。GetWindowDC函数很少使用,如果想尝试用一用它,则必须拦截处理WM_NCPAINT消息,Windows使用该消息在窗口的非显示区域上绘图。
BeginPaint、GetDC和GetWindowDC获得的设备内容都与视频显示器上的某个特定窗口相关。取得设备内容句柄的另一个更通用的函数是CreateDC:
invoke CreateDC,pszDriver, pszDevice, pszOutput, pDatamov hdc,eax其它行程序Invoke DeleteDC,hdc
例如,您可以通过下面的呼叫来取得整个屏幕的设备内容句柄:
invoke CreateDC, CTXT("DISPLAY"), pszDevice, pszOutput, pDatamov hdc,eax&&&
在窗口之外写入画面一般是不恰当的,但对于一些不同寻常的应用程序来说,这样做很方便(您还可通过在呼叫GetDC时使用一个NULL参数,从而取得整个屏幕的设备内容句柄,不过这在文件中已经提到了)。有时您只是需要取得关于某设备内容的一些信息而并不进行任何绘画,在这种情况下,您可以使用CreateIC来取得一个「信息内容」的句柄,其参数与CreateDC函数相同,例如:
invoke CreateIC, CTXT("DISPLAY"), pszDevice, pszOutput, pDatamov hdc,eax您不能用这个信息内容句柄往设备上写东西。使用位图时,取得一个「内存设备内容」有时是有用的:
invoke CreateCompatibleDC,hdcmov hdcMem,eax其它行程序invoke DeleteDC,hdcMem
您可以将位图选进内存设备内容,然后使用GDI函数在位图上绘画。&&
前面已经提到过,metafile是一些GDI呼叫的集合,以二进制形式编码。您可以通过取得metafile设备内容来建立metafile:invoke CreateMetaFile, pszFilenamemov hdcMeta,eax其它行程序invoke CloseMetaFile, hdcMeta&&&
在metafile设备内容有效期间,任何用hdcMeta所做的GDI呼叫都变成metafile的一部分而不会显示。在呼叫CloseMetaFile之后,设备内容句柄变为无效,函数传回一个指向metafile(hmf)的句柄。我会在后面一期讨论metafile。
取得设备内容信息
一个设备内容通常是指一个实际显示设备,如显示器和打印机。通常,您需要取得有关该设备的信息,包括显示器的大小(单位为像素或者实际长度单位)和色彩显示能力。您可以通过呼叫GetDeviceCaps(「取得设备功能」)函数来取得这些信息:
invoken GetDeviceCaps,hdc,iIndexmov iValue,eax
其中,参数iIndex取值为 Windows.inc 表头文件中定义的29个标识符之一。例如,iIndex为HORZRES时将使GetDeviceCaps传回设备的宽度(单位为像素);iIndex为VERTRES时将让GetDeviceCaps传回设备的高度(单位为像素)。如果hdc是打印机设备内容的句柄,则GetDeviceCaps传回打印机显示区域的高度和宽度,它们也是以像素为单位的。
还可以使用GetDeviceCaps来确定设备处理不同型态图形的能力,这对于视频显示器并不很重要,但是对于打印设备却是非常重要。例如,大多数绘图机不能画位图图像,GetDeviceCaps就可以将这一情况告诉您。DEVCAPS1程序所示的DEVCAPS1程序显示了以一个视频显示器的设备内容为参数时,可以从 GetDeviceCaps函数中获得的部分信息。
程序5-1 DEVCAPS1 DEVCAPS1.ASM
;MASMPlus 代码模板 - 普通的 Windows 程序代码
.Model Flat, StdCall
Option Casemap :None
Include windows.inc
Include user32.inc
Include kernel32.inc
Include gdi32.inc
includelib gdi32.lib
IncludeLib user32.lib
IncludeLib kernel32.lib
include macro.asm
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
WndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
NUMLINES equ (devcapsEnd - devcaps) / 4 /3
szAppName db "DevCaps1",0
szShow01 db "HORZSIZE",0
szShow02 db "Width in millimeters:",0
szShow03 db "VERTSIZE",0
szShow04 db "Height in millimeters:",0
szShow05 db "HORZRES",0
szShow06 db "Height in raster lines:",0
szShow07 db "VERTRES",0
szShow08 db "Width in pixels:",0
szShow09 db "BITSPIXEL",0
szShow10 db "Color bits per pixel:",0
szShow11 db "PLANES",0
szShow12 db "Window border width",0
szShow13 db "NUMBRUSHES",0
szShow14 db "Number of device brushes:",0
szShow15 db "NUMPENS",0
szShow16 db "Number of device pens:",0
szShow17 db "NUMMARKERS",0
szShow18 db "Number of device markers:",0
szShow19 db "NUMFONTS",0
szShow20 db "Number of device fonts:",0
szShow21 db "NUMCOLORS",0
szShow22 db "Number of device colors:",0
szShow23 db "PDEVICESIZE",0
szShow24 db "Size of device structure:",0
szShow25 db "ASPECTX",0
szShow26 db "Relative width of pixel:",0
szShow27 db "ASPECTY",0
szShow28 db "Relative height of pixel:",0
szShow29 db "ASPECTXY",0
szShow30 db "Relative diagonal of pixel:",0
szShow31 db "LOGPIXELSX",0
szShow32 db "Horizontal dots per inch:",0
szShow33 db "LOGPIXELSY",0
szShow34 db "Vertical dots per inch:",0
szShow35 db "SIZEPALETTE",0
szShow36 db "Number of palette entries:",0
szShow37 db "NUMRESERVED",0
szShow38 db "Reserved palette entries:",0
szShow39 db "COLORRES",0
szShow40 db "Actual color resolution:",0
devcaps dd HORZSIZE, offset szShow01, offset szShow02
dd VERTSIZE, szShow03,offset szShow04
dd HORZRES,offset szShow05,offset szShow06
dd VERTRES,offset szShow07,offset szShow08
dd BITSPIXEL,offset szShow09,offset szShow10
dd PLANES,offset szShow11,offset szShow12
dd NUMBRUSHES,offset szShow13,offset szShow14
dd NUMPENS,offset szShow15,offset szShow16
dd NUMMARKERS,offset szShow17,offset szShow18
dd NUMFONTS,offset szShow19,offset szShow20
dd NUMCOLORS,offset szShow21,offset szShow22
dd PDEVICESIZE,offset szShow23,offset szShow24
dd ASPECTX,offset szShow25,offset szShow26
dd ASPECTY,offset szShow27,offset szShow28
dd ASPECTXY,offset szShow29,offset szShow30
dd LOGPIXELSX,offset szShow31,offset szShow32
dd LOGPIXELSY,offset szShow33,offset szShow34
dd SIZEPALETTE,offset szShow35,offset szShow36
dd NUMRESERVED,offset szShow37,offset szShow38
dd COLORRES,offset szShow39,offset szShow40
devcapsEnd label DWORD
hInstance dd ?
cxChar dd ?
cxCaps dd ?
cyChar dd ?
START: ;从这里开始执行
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke WinMain,hInstance,NULL,NULL,SW_SHOWDEFAULT
invoke ExitProcess,0
WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,iCmdShow:DWORD
LOCAL wndclass :WNDCLASSEX
LOCAL msg :MSG
local hWnd :HWND
mov wndclass.cbSize,sizeof WNDCLASSEX
mov wndclass.style,CS_HREDRAW or CS_VREDRAW
mov wndclass.lpfnWndProc,offset WndProc
mov wndclass.cbClsExtra,0
mov wndclass.cbWndExtra,0
push hInst
pop wndclass.hInstance
invoke LoadIcon,NULL,IDI_APPLICATION
mov wndclass.hIcon,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wndclass.hCursor,eax
invoke GetStockObject,WHITE_BRUSH
mov wndclass.hbrBackground,EAX
mov wndclass.lpszMenuName,NULL
mov wndclass.lpszClassName,offset szAppName
mov wndclass.hIconSm,0
invoke RegisterClassEx, ADDR wndclass
.if (EAX==0)
invoke MessageBox,NULL,CTXT("This program requires Windows NT!"),addr
szAppName,MB_ICONERROR
invoke CreateWindowEx,
ADDR szAppName, ;window class name
CTXT("Device Capabilities"), ;window caption
WS_OVERLAPPEDWINDOW, ;window style
CW_USEDEFAULT, ;initial x position
CW_USEDEFAULT, ;initial y position
CW_USEDEFAULT, ;initial x size
CW_USEDEFAULT, ;initial y size
NULL, ;parent window handle
NULL, ;window menu handle
hInstance, ;program instance handle
NULL ;creation parameters
mov hWnd,eax
invoke ShowWindow,hWnd,iCmdShow
invoke UpdateWindow,hWnd
StartLoop:
invoke GetMessage,ADDR msg,NULL,0,0
cmp eax, 0
je ExitLoop
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
jmp StartLoop
mov eax,msg.wParam
WinMain endp
WndProc proc hwnd:DWORD,message:DWORD,wParam :DWORD,lParam :DWORD
LOCAL szBuffer[10] :BYTE
LOCAL hdc :HDC
LOCAL i :DWORD
LOCAL ps :PAINTSTRUCT
LOCAL tm :TEXTMETRIC
LOCAL tmp1,tmp2 :DWORD
.if message==WM_CREATE
invoke GetDC,hwnd
mov hdc,eax
invoke GetTextMetrics,hdc,addr tm
mov eax,tm.tmAveCharWidth
mov cxChar,cxChar = tm.tmAveCharWidth
mov eax,2 ;cxCaps = (tm.tmPitchAndFamily & 1 ? 3 : 2) * cxChar / 2
test DWORD ptr tm.tmPitchAndFamily,1
mov eax,cxChar
mov cxCaps,eax
mov eax,tm.tmHcyChar = tm.tmHeight + tm.tmExternalLeading
add eax,DWORD ptr tm.tmExternalLeading
mov cyChar,eax
invoke ReleaseDC,hwnd, hdc
.elseif message == WM_PAINT
invoke BeginPaint,hwnd,addr ps
mov hdc,eax
mov DWORD ptr i,0
lea esi,devcaps
add esi,4 ;指向后面的字符串的地址
mov eax,cyCtmp2 = cyChar * i
mov tmp2,eax
mov edi,[esi] ;esi指向字符串的地址
;edi指向字符串
invoke lstrlen,取字符串长度
mov ebx,eax
;TextOut(hdc, 0, cyChar * i,devcaps[i].szLabel,lstrlen (devcaps[i].szLabel))
invoke TextOut,hdc,0,tmp2,edi,ebx
mov ebx,16 ;ecx = cxCaps * 14 原代码为14,但我发现具体太近遂做一点修改
mov eax,cxCaps
mov tmp1,eax
mov edi,[esi] ;指向一个字符串地址
invoke lstrlen,edi
mov ebx,eax
;TextOut(hdc, 14 * cxCaps, cyChar * i,devcaps[i].szDesc,lstrlen (devcaps[i].szDesc))
invoke TextOut,hdc,tmp1,tmp2,edi,ebx
invoke SetTextAlign,hdc,TA_RIGHT or TA_TOP
mov eax,cxCtmp=14 * cxCaps + 35 * cxChar
mov ebx,35
add tmp1,eax
mov edi,[esi] ;edi=devcaps[i].iIndex
invoke GetDeviceCaps,hdc,edi
invoke wsprintf,addr szBuffer,CTXT("%5d"),eax
mov ebx,eax
invoke TextOut,hdc,tmp1,tmp2,addr szBuffer,ebx
invoke SetTextAlign,hdc,TA_LEFT or TA_TOP
add esi,16
cmp DWORD ptr i,NUMLINES
invoke EndPaint,hwnd,addr ps
.elseif message == WM_DESTROY
invoke PostQuitMessage,NULL
invoke DefWindowProc,hwnd, message, wParam, lParam
WndProc endp
可以看到,这个程序非常类似前面的SYSMETS1程序。为了保持程序代码的短小,我没有使用滚动条,因为我知道信息可以在一个画面上显示出来。运行结果如下:
设备的大小
假定要绘制边长为1英寸的正方形,您(程序写作者)或Windows(操作系统)需要知道视频显示上1英寸对应多少像素。使用GetDeviceCaps函数能取得有关如视频显示器和打印机之类输出设备的实际显示大小信息。
视频显示器和打印机是两个不同的设备。但也许最不明显的区别是「分辨率」与设备联系起来的方式。对于打印机,我们经常用「每英寸的点数(dpi)」表示分辨率。例如,大多数激光打印机有300或600dpi的分辨率。然而,视频显示器的分辨率是以水平和垂直的总像素数来表示的,例如,。大多数人不会告诉您他的打印机在一张纸上水平和垂直打印多少像素或他们的视频显示器上每英寸有多少像素。在本书中,我用「分辨率」来严格定义每度量单位(一般为英寸)内的像素数。我使用「像素大小」或「像素尺寸」表示设备水平或垂直显示的总像素数。「度量大小」或「度量尺寸」是以英寸或毫米为单位的设备显示区域的大小。(对于打印机页面,它不是整个页面,只是可打印的区域。)像素大小除以度量大小就得到分辨率。
现在Windows使用的大多数视频显示器的屏幕都是宽比高多33%。这就表示纵横比为1.33:1或(一般写法)4:3。历史上,该比例可追溯到Thomas
Edison制作电影的年代。它一直作为电影的标准纵横比,直到1953年出现各种型态的宽银幕投影机。电视机屏幕的纵横比也是4:3。
然而,Windows应用程序不应假设视频显示器具有4:3的纵横比。人们进行文字处理时希望视频显示器与一张纸的长和宽类似。最普通的选择是把4:3变为3:4显示,把标准显示翻转一下。
如果设备的水平分辨率与垂直分辨率相等,就称设备具有「正方形像素」。现在,Windows普遍使用的视频显示器都具有正方形像素,但也有例外。(应用程序也不应假设视频显示器总是具有正方形像素。)Windows第一次发表时,标准显示卡卡是IBM
Color Graphics Adapter(CGA),它有640×200的像素大小;Enhanced Graphics
Adapter(EGA)有640×350的像素大小;Hercules Graphics
Card有720×348的像素大小。所有这些显示卡都使用4:3纵横比的显示器,但是水平和垂直像素数的比值都不是4:3。
执行Windows的使用者很容易确定视频显示器的像素大小。在「控制面板」中选择「显示」,并选择「设定」页面标签。在标有「屏幕分辨率」的字段中,可以看到这些像素尺寸之一:
640×480像素
800×600像素
所有这些都是4:3。(除了像素大小。这不但有些不好,还有些令人反感。所有这些像素尺寸都认为在4:3的显示器上会产生正方形的像素。)
Windows应用程序可以使用SM_CXSCREEN和SM_CYSCREEN参数从GetSystemMetrics得到像素尺寸。从DEVCAPS1程序中您会注意到,程序可以用HORZRES(水平分辨率)和VERTRES参数从GetDeviceCaps中得到同样的值。这里「分辨率」指的是像素大小而不是每度量单位的像素数。
这些是设备大小的简单部分,现在开始复杂的部分。
前两个设备能力,HORZSIZE和VERTSIZE,文件中称为「以毫米计的实际屏幕的宽度」及「以毫米计的实际屏幕的高度」。这些看起来更像直接的定义。例如,给出视频显示卡和显示器的接口特性,Windows如何真正知道显示器的大小呢?如果您有台笔记本电脑(它的视频驱动程序能知道准确的屏幕大小)并且连接了外部显示器,又是哪种情况呢?如果把视频投影机连接到计算机上呢?
在Windows的16位版本中(及在Windows NT中),Windows为HORZSIZE和VERTSIZE使用「标准」的显示大小。然而,从Windows
95开始,HORZSIZE和VERTSIZE值是从HORZRES、VERTRES、LOGPIXELSX和LOGPIXELSY值中衍生出来的。这是它的工作方式。
当您在「控制面板」中使用「显示」程序选择显示的像素大小时,也可以选择系统字体的大小。这个选项的原因是用于640×480显示的字体在提升到或更大时字太小,而您可能想要更大的系统字体。这些系统字体大小指「显示」程序的「设定」页面卷标中的「小字体」和「大字体」。
在传统的排版中,字体的字母大小由「点」表示。1点大约1/72英寸,在计算机排版中1点正好为1/72英寸。
理论上,字体的点值是从字体中最高的字符顶部到例如j、p、q和y等字母下部的字符底部的距离,其中不包括重音符号。例如,在10点的字体中此距离是10/72英寸。根据TEXTMETRIC结构,字体的点值等于tmHeight字段减去tmInternalLeading字段,如图5-2所示(该图与上一期的图一样)。 
在真正的排版中,字体的点值与字体字母的实际大小并不正好相等。字体的设计者做出的实际字符比点值指示的要大一些或小一些。毕竟,字体设计是一种艺术而不是科学。
TEXTMETRIC结构的tmHeight字段指出文字的连续行在屏幕或打印机上间隔的方式。这也可以用点来测量。例如,12点的行距指出文字连续行的基准线应该间隔12/72(或1/6)英寸。不应该为10点字体使用10点行距,因为文字的连续行会碰到一起。
10点字体读起来很舒服。小于10点的字体不益于长时间阅读。
Windows系统字体-不考虑是大字体还是小字体,也不考虑所选择的视频像素大小-固定假设为10点字体和12点行距。这听起来很奇怪,如果字体都是10点,为什么还把它们称为大字体和小字体呢?
解答是:当您在「控制面板」的「显示」程序上选择小字体或大字体时,实际上是选择了一个假定的视频显示分辨率,单位是每英寸的点数
。当选择小字体时,即要Windows假定视频显示分辨率为每英寸96点。当选择大字体时,即要Windows假定视频显示分辨率为每英寸120点。
再看看上面的图5-2。那是小字体,它依据的显示分辨率为每英寸96点。我说过它是10点字体。10点即是10/72英寸,如果乘以96点,每英寸大概就为13像素。这即是tmHeight减去tmInternalLeading的值。行距是12点,或12/72英寸,它乘以96点,每英寸就为16像素。这即是tmHeight的值。
图5-3显示大字体。这是依据每英寸120点的分辨率。同样,它是10点字体,10/72乘以120点,每英寸等于16像素,即是tmHeight减tmInternalLeading的值。12点行距等于20像素,即是tmHeight的值。(像前一章一样,再次强调所显示的是实际的度量大小,因此您可以理解它工作的方式。不要在您的程序中对此写作程序。)
 &&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&
图5-3 大字体和FONTMETRIC字段&&&
在Windows程序中,您可以使用GetDeviceCaps函数取得使用者在「控制面板」的「显示器」程序中选择的以每英寸的点数为单位的假定分辨率。要得到这些值(如果视频显示器不具有正方形像素,在理论上这些值是不同的),可以使用索引LOGPIXELSX和LOGPIXELSY。LOGPIXELS指逻辑像素,它的基本意思是「以每英寸的像素数为单位的非实际分辨率」。
用HORZSIZE和VERTSIZE索引从GetDeviceCaps得到的设备能力,在文件上称为「实际屏幕的宽度,单位毫米」及「实际屏幕的高度,单位毫米」。因为这些值是从HORZRES、VERTRES、LOGPIXELSX和LOGPIXELSY值中衍生出来的,所以它们应该称为「逻辑宽度」和「逻辑高度」。公式是: 
常数25.4用于把英寸转变为毫米。
这看起来是种不合逻辑的退步。毕竟,视频显示器是可以用尺以毫米为单位的大小(至少是近似的)衡量的。但是Windows并不关心这个大小。相反,它以使用者选择的显示像素大小和系统字体大小为基础计算以毫米为单位的显示大小。更改显示的像素大小并根据GetDeviceCaps更改度量大小。这有什么意义呢?
这非常有意义。假定有一个17英寸的显示器。实际的显示大小大约是12英寸乘9英寸。假定在最小要求的640×480像素大小下执行Windows。这意味着实际的分辨率是每英寸53点。10点字体(在纸上便于阅读)在屏幕上从A的顶部到q的底部只有7个像素。这样的字体很难看而且不易读。(可问问那些在旧的Color
Graphics Adapter上执行Windows的人们。)
现在,把您的计算机接上视频投影机。投影的视频显示器是4英尺宽,3英尺高。同样的640×480像素大小现在是大约每英寸13点的分辨率。在这种条件下试图显示10点的字体是很可笑的。
10点字体在视频显示器上应是可读的,因为它在打印时是肯定可读的。所以10点字体就成为一个重要的参照。当Windows应用程序确保10点屏幕字体为平均大小时,就能够使用8点字体显示较小的文字(仍可读),或用大于10点的字体显示较大的文字。因而,视频分辨率(以每英寸的点数为单位)由10点字体的像素大小来确定是很有意义的。然而,在Windows
NT/XP中,用老的方法定义HORZSIZE和VERTSIZE值。这种方法与Windows的16位版本一致。HORZRES和VERTRES值仍然表示水平和垂直像素的数值,LOGPIXELSX和LOGPIXELSY仍然与在「控制面板」的「显示器」程序中选择的字体有关。在Windows
98中,LOGPIXELSX和LOGPIXELSY的典型值是96和120 dpi,这取决于您选择的是小字体还是大字体。
NT/XP中的区别是HORZSIZE和VERTSIZE值固定表示标准显示器大小。对于普通的显示卡,取得的HORZSIZE和VERTSIZE值分别是320和240毫米。这些值是相同的,与选择的像素大小无关。因此,这些值与用HORZRES、VERTRES、LOGPIXELSX和LOGPIXELSY索引从GetDeviceCaps中得到的值不同。然而,可以用前面的公式计算在Windows
98下的HORZSIZE和VERTSIZE值。
如果程序需要实际的视频显示大小该怎么办?也许最好的解决方法是用对话框让使用者输入它们。
最后,来自GetDeviceCaps的另三个值与视频大小有关。ASPECTX、ASPECTY和ASPECTXY值是每一个像素的相对宽度、高度和对角线大小,四舍五入到整数。对于正方形像素,ASPECTX和ASPECTY值相同。无论如何,ASPECTXY值应等于ASPECTX与ASPECTY平方和的平方根,就像直角三角形一样。
如果视频显示卡仅显示黑色像素和白色像素,则每个像素只需要内存中的一位。彩色显示器中每个像素需要多个位。位数越多,色彩越多,或者更具体地说,可以同时显示的不同色彩的数目等于2的位数次方。
「Full-Color」视频显示器的分辨率是每个像素24位-8位红色、8位绿色以及8位蓝色。红、绿、蓝即「色光三原色」。混合这三种基本颜色可以生成许多其它的颜色,您通过放大镜看显示屏,就可以看出来。
「High-Color」显示分辨率是每个像素16位-5位红色、6位绿色以及5位蓝色。绿色多一位是因为人眼对绿色更敏感一些。
显示256种颜色的显示卡每个像素需要8位。然而,这些8位的值一般由定义实际颜色的调色盘组织的。关于这方面,我们在后面会更详细地讨论它们。
最后,显示16种颜色的显示卡每个像素需要4位。这16种颜色一般固定分为暗的或亮的红、黑、蓝、青、紫、黄、两种灰色。这16种颜色要回溯到老式的IBM CGA。
只有在某些怪异的程序中才需要知道视频显示卡上的内存是如何组织的,但是GetDeviceCaps使程序写作者可以知道显示卡的储存组织以及它能够表示的色彩数目,下面的呼叫传回色彩平面的数目:
invoke GetDeviceCaps,hdc,PLANESmov iPlanes, eax
下面的呼叫传回每个像素的色彩位数:
invoke GetDeviceCaps,hdc, BITSPIXELmov iPlanes, eax
大多数彩色图形显示设备使用多个色彩平面或每像素有多个色彩位的设计,但是不能同时一齐使用这两种方式;换句话说,这两个呼叫必有一个传回1。显示卡能够表示的色彩数可以用如下公式来计算:
iColors = 2 ^ (iPlanes * iBitsPixel) ;
这个值与用NUMCOLORS参数得到的色彩数值可能一样,也可能不一样:
invoke GetDeviceCaps,hdc, NUMCOLORSmov iColors, eax
我提到过,256色的显示卡使用色彩调色盘。在那种情况下,以NUMCOLORS为参数时,GetDeviceCaps传回由Windows保留的色彩数,值为20,剩余的236种颜色可以由Windows程序用调色盘管理器设定。对于High-Color和True-Color显示分辨率,带有NUMCOLORS参数的GetDeviceCaps通常传回-1,这样就无法得到需要的信息,因此应该使用前面所示的带有PLANES和BITSPIXEL值的iColors公式。
在大多数GDI函数呼叫中,使用COLORREF值(只是一个32位的无正负号长整数)来表示一种色彩。COLORREF值按照红、绿和蓝色的亮度指定了一种颜色,通常叫做「RGB色彩」
。32位的COLORREF值的设定如图5-4所示。 
图5-14 七种画笔样式
对于PS_SOLID、PS_NULL和PS_INSIDEFRAME画笔样式,iWidth参数是画笔的宽度。iWidth值为0则意味着画笔宽度为一个像素。现有画笔是一个像素宽。如果指定的是点划线或者虚线式画笔样式,同时又指定一个大于1的实际宽度,那么Windows将使用实线画笔来代替。
CreatePen的crColor参数是一个COLORREF值,它指定画笔的颜色。对于除了PS_INSIDEFRAME之外的画笔样式,如果将画笔选入设备内容中,Windows会将颜色转换为设备所能表示的最相近的纯色。PS_INSIDEFRAME是唯一一种可以使用混色的画笔样式,并且只有在宽度大于1的情况下才如此。
在与定义一个填入区域的函数一起使用时,PS_INSIDEFRAME画笔样式还有另外一个奇特之处:对于除了PS_INSIDEFRAME以外的所有画笔样式来说,如果用来画边界框的画笔宽度大于1个像素,那么画笔将居中对齐在边界框在线,这样边界框线的一部分将位于边界框之外;而对于PS_INSIDEFRAME画笔样式来说,整条边界框线都画在边界框之内。
您也可以通过建立一个型态为LOGPEN(「逻辑画笔」)的结构,并呼叫CreatePenIndirect来建立画笔。如果您的程序使用许多能在原始码中初始化的画笔,那么使用这种方法将有效得多。
要使用CreatePenIndirect,首先定义一个LOGPEN型态的结构:
此结构有三个成员:lopnStyle(无正负号整数或UINT)是画笔样式,lopnWidth(POINT结构)是按逻辑单位度量的画笔宽度,lopnColor
(COLORREF)是画笔颜色。Windows只使用lopnWidth结构的x值作为画笔宽度,而忽略y值。
将结构的地址传递给CreatePenIndirect结构就可以建立画笔了:
hPen = CreatePenIndirect (&logpen) ;&&&
注意,CreatePen和CreatePenIndirect函数不需要设备内容句柄作为参数。这些函数建立与设备内容没有联系的逻辑画笔。直到呼叫SelectObject之后,画笔才与设备内容发生联系。因此,可以对不同的设备(如屏幕和打印机)使用相同的逻辑画笔。
下面是建立、选择和删除画笔的一种方法。假设您的程序使用三种画笔-一种宽度为1的黑画笔、一种宽度为3的红画笔和一种黑色点式画笔,您可以先定义三个变量来存放这些画笔的句柄:
static HPEN hPen1, hPen2, hPen3 ;&&&
在处理WM_CREATE期间,您可以建立这三种画笔:
hPen1 = CreatePen (PS_SOLID, 1, 0) ;hPen2 = CreatePen (PS_SOLID, 3, RGB (255, 0, 0)) ;hPen3 = CreatePen (PS_DOT, 0, 0) ;&&&
在处理WM_PAINT期间,或者是在拥有一个设备内容有效句柄的任何时间里,您都可以将这三个画笔之一选进设备内容并用它来画线:
SelectObject (hdc, hPen2) ;
SelectObject (hdc, hPen1) ;
其它画线函数
在处理WM_DESTROY期间,您可以删除您建立的三种画笔:
DeleteObject (hPen1) ;DeleteObject (hPen2) ;DeleteObject (hPen3) ;&&&
这是建立、选择和删除画笔最直接的方法。但是您的程序必须知道执行期间需要哪些逻辑画笔,为此,您可能想要在每个WM_PAINT消息处理期间建立画笔,并在呼叫EndPaint之后删除它们(您可以在呼叫EndPaint之前删除它们,但是要小心,不要删除设备内容中目前选择的画笔)。您可能还希望随时建立画笔,并将CreatePen和SelectObject呼叫组合到同一个叙述中:SelectObject (hdc, CreatePen (PS_DASH, 0, RGB (255, 0, 0))) ;&&&
现在再开始画线,您将使用一个红色虚线画笔。在画完红色虚线之后,可以删除画笔。糟了!由于没有保存画笔句柄,怎么才能删除这些画笔呢?不要紧,请记住,SelectObject将传回设备内容中上一次选择的画笔句柄。所以,您可以通过呼叫SelectObject将BLACK_PEN选进设备内容,并删除从SelectObject传回的值:DeleteObject (SelectObject (hdc, GetStockObject (BLACK_PEN))) ;
下面是另一种方法,在将新建立的画笔选进设备内容时,保存SelectObject传回的画笔句柄:hPen = SelectObject (hdc, CreatePen (PS_DASH, 0, RGB (255, 0, 0))) ;
现在hPen是什么呢?如果这是在取得设备内容之后第一次呼叫SelectObject,则hPen是BLACK_PEN对象的句柄。现在,可以将hPen选进设备内容,并删除所建立的画笔(第二次SelectObject呼叫传回的句柄),只要一道叙述即可:DeleteObject (SelectObject (hdc, hPen)) ;
如果有一个画笔的句柄,就可以通过呼叫GetObject取得LOGPEN结构各个成员的值:GetObject (hPen, sizeof (LOGPEN), (LPVOID) &logpen) ;
如果需要目前选进设备内容的画笔句柄,可以呼叫:hPen = GetCurrentObject (hdc, OBJ_PEN) ;后面讨论另一个建立画笔的函数ExtCreatePen。
内容很多,继续看下一节>>>>
欢迎访问AoGo汇编小站:

我要回帖

更多关于 xstart 的文章

 

随机推荐