GDI

常用GDI绘图函数

 

函   数                                           说   明
CreateBrushIndirect           在一个LOGBRUSH数据结构的基础上创建一个刷子
CreateDIBPatternBrush      用一幅与设备无关的位图创建一个刷子,以便指定刷子样式(图案)
CreateDIBPatternBrushPt   用一幅与设备无关的位图创建一个刷子,以便指定刷子样式(图案)
CreateHatchBrush               创建带有阴影图案的一个刷子(阴影图案见注解)
CreatePatternBrush            用指定了刷子图案的一幅位图创建一个刷子
CreatePen                           用指定的样式、宽度和颜色创建一个画笔
CreatePenIndirect               根据指定的LOGPEN结构创建一个画笔
CreateSolidBrush                 用纯色创建一个刷子
ExtCreatePen                      创建一个扩展画笔(装饰或几何)
GetStockObject                    取得一个固有对象(Stock)。这是可由任何应用程序使用的windows标准对象之一


例1∶创建一个红色实线画笔,画笔宽度为3个像素点
Dim NewPen As Long
Private Const PS_SOLID = 0
NewPen&=CreatePen (PS_SOLID,3,RGB(255,0,0))
注∶其中PS_SOLID常数代表实线
例2∶创建阴影刷子
LOGBRUSH结构的定义如下∶
Private Type LOGBRUSH
        lbStyle As Long
        lbColor As Long
        lbHatch As Long
End Type
以下代码餍了创建一个 刷子样式为 阴影(BS_HATCHED) ,阴影类型为十字交叉(HS_CROSS)的红色画笔。
Dim BrushInfo As LOGBRUSH
BrushInfo.lbStyle = BS_HATCHED

BrushInfo.lbColor = RGB(255,0,0)
BrushInfo.lbHatch = HS_CROSS
NewBrush = CreateBrushIndirect(BrushInfo)

例3∶用纯色创建刷子(这个例子中是红色)
    NewBrush =CreateSolidBrush(vbRed)


同样,用其他几个函数,按照其用法可以创建相应的GDI绘图对象。现在,您大概了解有以上这些函数和,理解给出的几个例子就可以了。稍后,我们结合实际例子,更深入地探讨这些函数的用法。

三、拿起和放下画笔(GDI对象)

      现在我感觉好象向一群绘画系的学生讲课,尽管自己不怎么会绘画。首先是练基本功,怎样拿起画笔和放下画笔,这可能是绘画专业学生首先要学习的吧?当然,更广义地讲应当是怎样选择和删除GDI绘图对象。

 

      在通过前述方法来创建一个GDI对象句柄(上例中的NewBrush,NewPen等)以后,为了使用它们,我们必须用SelectObjecth API函数把它们选入相应的设备场景。一个设备场景在某一时刻、在每一种类型中只能拥有一个对象,如一个画笔和一个刷子一个位图等。
      SelectObjecth函数的用法非常简单,需要记住的是,此被调用后,如果成功将返回旧的对象句柄。你需要把它保存起来。当然,这一过程只需要把返回值附值于某一Long型变量就可以了。如∶

OldPen&=SelectObject(Picture1.hDC , NewPen&)

     接下来该做什么呢?对对,这位同学说的对∶绘图。该怎么绘图呢?不,不,不要着急,这个问题,我们留在下一节中讨论,现在你只需记住,这里可以画些圆呀、矩形呀、添充多边型呀的操作。那么,绘图操作结束以后该怎么办呢?答案是∶应当把旧的绘图对象回设到设备场景中去。如下∶

    SelectObject Picture1.hDC,OldPen&

      这样,设备场景将恢复到我们为其选入绘图对象以前的状态。因为,我们不能断定其他绘图函数会使用什么样的绘图对象。因此把原来的绘图对象放回去乃是一个上策。但,如果你接着要为设备场景选择另一个对象,这个步骤可以留在后面进行。那么在这次的选入过程中就没有必要保存旧的对象句柄了,这是因为SelectObject函数返回的旧对象的句柄就是刚才我们为其选择的句柄。
     绘图也完了,设备场景也恢复了原始状况,那么就操作告一段落了吗?不,还有一点。您最好把您自己创建的GDI对象删除掉,释放掉刚才使用过的资源。操作如下∶

DeleteObject NewPen
又如∶
DeleteObject NewBrush
      其实,您不删除这些对象资源,应用程序退出时会自动释放的。这是因为在Win32中,资源为每个应用程序私有的。由于这种原因,应用程序之间也不能共享一个GDI对象。但是,删除你所创建的GDI对象仍是一个好的编程习惯。既然不用,留着它做什么呢,何必占用资源空间呢?
     外,您千万千万切记,切记,千万∶不要删除已经选入设备场景的系统GDI对象。
     还有一点,GetStockObject函数返回的对象是系统对象,请不要用DeleteObject函数删除它,否则会出现非常非常可怕的事情———你的硬盘将被永远用不了啦。@@~ 呵呵,吓唬你一把,其实没那么严重。不过,我想您大概不是明知整了坏,偏向坏里整的人吧?
      如果是的话,随便整好了。

      OK,以下展示了使用GDI对象的API函数。

函   数                                       说   明
DeleteObject           用这个函数删除GDI对象,比如画笔、刷子、字体、位图、区域以及调色板等等。对象使用的所有系统资源都会被释放
EnumObjects           枚举可随同指定设备场景使用的画笔和刷子
GetCurrentObject    用于获得指定类型的当前选定对象
GetObjectAPI           取得对指定对象进行说明的一个结构。windows手册建议用GetObject 这个名字来引用该函数。GetObjectAPI在vb中用于避免与GetObject关键字混淆
GetObjectType        判断由指定句柄引用的GDI对象的类型
SelectObject            每个设备场景都可能有选入其中的图形对象。其中包括位图、刷子、字体、画笔以及区域等等。一次选入设备场景的只能有一个对象。选定的对象会在设备场景的绘图操作中使用。例如,当前选定的画笔决定了在设备场景中描绘的线段颜色及样式
       除了DeleteObject和SelectObject以外的其他函数用于从系统或指定设备场景中获取有关GDI对象的信息,一般不十分常用。
      这样,假如我们要用画笔和刷子来做一些绘图朝着的话,编写代码的大概步骤是这样的。
Dim NewPen As Long
Dim NewBrush As Long
Dim OldPen As Long
Dim OldBrush As Long

NewPen& = CreatePen(PS_SOLID, 3, RGB(255, 0, 0)) * 创建画笔
NewBrush& = CreateSolidBrush(vbRed) * 创建画刷

 


OldPen& = SelectObject(Picture1.hDC,NewPen&)
      '添加绘图操作代码

OldBrush& = SelectObject(Picture1.hDC,NewBrush)
      '添加填充操作代码
SelectObject Picture1.hDC,OldPen&
SelectObject Picture1.hDC,OldBrush&
DeleteObject NewPen&
DeleteObject NewBrush&
      注意一点,要用API绘图函数,并非一定要创建画笔和刷子。完全可以使用现有的GDI对象,直接调用函数来绘图。记住,设备场景中总有个默认的画笔和刷子,问题是它符不符合您的要求了。但我觉得,在绘图之前选择画笔是个好习惯。有些VB功能也可以结合使用,比如你想用红色画笔,你可以设置Forcolor属性为红色、想加宽画笔的宽度,可以设置DrawWidth属性等。

四、绘图属性与绘图函数

      到目前为止,我们已经学会了绘图所需要的一切准备工作。上一节中最后给出的代码就说明这一点。代码中,现在只缺少具体的绘图代码。本节就讨论关于如何绘图的问题。
      在接触绘图函数之前,首先需要了解绘图属性。设备场景定义了一系列绘图属性。这些绘图属性定义了刷子和画笔与窗口或设备表面当前内容相互作用的方法。比如,当前画笔的位置、当前背景颜色、圆弧和矩形的绘制方向、光栅操作模式等等。虽然后面给出了很多属性控制函数,但用VB自身的函数和方法属性,更容易实现。比如,设置背景模式,只要设置控件的BackColor属性就可以很轻松、带愉快地完成。但是,如果是要在一个不与窗口关联的自建设备场景中绘图的话,想必依靠这些函数是不可逃避的。

      线光栅操作∶我们已经知道,光栅操作是一种位操作。通常你想用画笔进行绘图时,都假定画笔色彩只是简单的绘制到显示器或设备上。实际上,WINDOWS支持16种不同的线绘图模式,它们定义了一条线如何与显示器上已有的信息组合。这些模式就叫做线光栅操作(有时叫ROP2模式)。并且它们被作为绘图模式引入到了VisualBasic。ROP2光栅操作相当于设置VB的DrawMode属性。
      背景模式∶阴影刷子、虚线画笔和文本都有一个背景。对于阴影刷,它是指阴影线之间的区域,对于虚线画笔,则指点和虚线之间的区域。而对于文本,它是指每个字符单元的背景。背景模式决定了WINDOWS如何处理这些背景区。它可以是不透明的,也可以是透明的。若是不透明的,则背景区设置为背景色;否则如果是透明的,则背景区域保持原状。

      当前位置∶在VB中,要画一条直线其实非常简单,采用Line方法就可以,而且能够在一个语句中表达完成。如Line (5,5)-(10,10)
但在API中并不这样简单了(但也不是太麻烦)。要画直线,需要首先设定直线的起点。一般用MoveToEx函数来完成。然后在下一行代码中绘制直线,如LineTo 10,10。MoveToEx函数是经常使用的函数之一,用来确定绘图前的起始位置。。

      绘图属性控制函数

函   数                                          说   明
GetArcDirection              画圆弧的时候,判断当前采用的绘图方向
GetBkColor                    取得指定设备场景当前的背景颜色
GetBkMode                    针对指定的设备场景,取得当前的背景填充模式
GetCurrentPositionEx    在指定的设备场景中取得当前的画笔位置
GetMiterLimit                 取得设备场景的斜率限制(Miter)设置——斜率限制是指斜角长度与线宽间的比率
GetNearestColor           根据设备的显示能力,取得与指定颜色最接近的一种纯色
GetPolyFillMode             针对指定的设备场景,获得多边形填充模式。
GetROP2                        针对指定的设备场景,取得当前的绘图模式。这样可定义绘图操作如何与正在显示的图象合并起来
MoveToEx                      为指定的设备场景指定一个新的当前画笔位置。
SetArcDirection              设置圆弧的描绘方向

 

SetBkColor                     为指定的设备场景设置背景颜色。背景颜色用于填充阴影刷子、虚线画笔以及字符(如背景模式为OPAQUE)中的空隙。也在位图颜色转换期间使用。
SetBkMode                     指定阴影刷子、虚线画笔以及字符中的空隙的填充方式
SetMiterLimit                  设置设备场景当前的斜率限制
SetPolyFillMode              设置多边形的填充模式。
SetROP2                         设置指定设备场景的绘图模式。与vb的DrawMode属性完全一致。

      同VisualBasic相比较,API提供了功能更强大的绘图函数。大部分绘图函数的用法都非常简单明了,只要按其说明使用就可以,觉得没有必要我多加说明。

     WindoesAPI绘图函数

函   数                                                    说   明
AngleArc                          用一个连接弧画一条线,参考注解
Arc                                    画一个圆弧
ArcTo                                画一个圆弧,并更新当前位置
CancelDC                         取消另一个线程里的长时间绘图操作
Chord                               画一条弦线(椭圆的平分线)
Ellipse                              描绘一个椭圆,由指定的矩形围绕。椭圆用当前选择的画笔描绘,并用当前选择的刷子填充
FillRect                             用指定的刷子填充一个矩形
FloodFill                            用当前选定的刷子在指定的设备场景中填充一个区域。区域是由颜色crColor定义的
FrameRect                        用指定的刷子围绕一个矩形画一个边框(组成一个帧),边框的宽度是一个逻辑单位
GetPixel                            在指定的设备场景中取得一个指定像素的当前RGB值
InvertRect                         通过反转每个像素的值,从而反转一个设备场景中指定的矩形
LineDDA                            枚举指定线段中的所有点
Pie                                    画一个扇形
PolyBezier                        绘一条或多条贝塞尔(Bezier)曲线。
PolyBezierTo                     绘一条或多条贝塞尔(Bezier)曲线,并将当前画笔位置设为前一条曲线的终点
PolyDraw                          描绘一条复杂的曲线,由线段及贝塞尔曲线组成
Polygon                            描绘一个多边形,由两点或三点的任意系列构成。windows会将最后一个点与第一个点连接起来,从而封闭多边形。多边形的边框用当前选定的画笔描绘,多边形用当前选定的刷子填充
Polyline                            用当前画笔描绘一系列线段。使用PolylineTo函数时,当前位置会设为最后一条线段的终点。它不会由Polyline函数改动
PolylineTo                        同上,并设置当前画笔位置用当前选定画笔描绘两个或多个多边形。根据由SetPolyFillMode函数指定的多边形填充模式,用当前选定的刷子填充它们。每个多边形都必须是封闭的
PolyPolygon                     用当前选定画笔描绘两个或多个多边形。根据由SetPolyFillMode函数指定的多边形填充模式,用当前选定的刷子填充它们。每个多边形都必须是封闭的
PolyPolyline                     用当前选定画笔描绘两个或多个多边形
Rectangle                        用当前选定的画笔描绘矩形,并用当前选定的刷子进行填充
RoundRect                       用当前选定的画笔画一个圆角矩形,并用当前选定的刷子在其中填充。X3和Y3定义了用于生成圆角的椭圆
SetPixel                           在指定的设备场景中设置一个像素的RGB值,并返回该点的颜色
SetPixelV                         在指定的设备场景中设置一个像素的RGB值

 


      我把上表中大部分的函数的用法例举到了本教程附带的program1.vbp中。另外,
Bezier曲线的用法比较有趣。如果你用过3D Studio三维动画制作软件就知道,其中的很多绘图工作,尤其是二维平面绘图,就是采用Bezier曲线技术。本教程附带的program3.vbp
      程序简单展示了这种技术的应用。《前线》网站源码解析中的第24号(滤波器演示程序)、第26号(如何用指定颜色填充不规则封闭线框区域)等程序,也是这里部分函数的好例程,可以下载看看。

       Windows还提供了一些更特殊的绘图函数,你可以在Windows的内部用它们来绘制控件外框、标题栏、3D控件和桌面等系统对象。

    Win32 API其他绘图函数

函   数                                              说   明
DrawEdge                    用指定的样式(包括3D效果)描绘一个矩形的边框
DrawEscape                 换码(Escape)函数将数据直接发至显示设备驱动程序(在vb里使用:能够使用。但由于Escape对设备有较强的依赖性,所以除非万不得以,尽量不要用它)
DrawFocusRect            画一个焦点矩形。这个矩形是在标志焦点的样式中通过异或运算完成的(焦点通常用一个点线表示)。如用同样的参数再次调用这个函数,就表示删除焦点矩形
DrawFrameControl       这个函数用于描绘一个标准控件。例如,可描绘一个按钮或滚动条的帧
DrawState                    这个函数可为一幅图象或绘图操作应用各式各样的效果
GdiFlush                        在绘图操作前注意队列。 执行任何未决的绘图操作。注释
GdiGetBatchLimit           判断有多少个GDI绘图命令位于队列中
GdiSetBatchLimit           指定有多少个GDI绘图命令能够进入队列
PaintDesktop                在指定的设备场景中描绘桌面墙纸图案

      这里有几个函数很有趣,比如DrawEdge、DrawFrameControl。使用他们可以非常轻松地绘出按钮控件、编辑框控件等的外观。我已经把常用的函数的用法包含到了附带程序program2.vbp。

五、路 径

     应当说,路径是较为高级的话题,尽管它不是难于理解的。我学到的有关路径的知识,来自于Dan的《Visual Basic 5.0 WIN32开发人员指南》一书中的不到两页的内容中,在其他的书中尚未看到。
     糟糕的是路径没有句柄,所以说它不是GDI对象的成员。不过,千万要记住一点,任何一个设备场景只有一个路径。从这一点来看,就算为路径设置了句柄也是多余的。从我的感觉来看,路径像是在一个设备场景中绘出的任意形状的多边形区域(尽管它不是区域)。

      我在路径中体验出的一个好处就是,创建一个路径后,可以把它转换为区域。这一点可以用PathToRegion函数来完成。一旦这一步成功了就好办了,得到区域句柄以后就可以和其他区域对象一样处理了。总而言之,通过路径我们可以很轻松地创建复杂的图形区域。
     创建一个路径非常简单。具体形式如下∶

        dl& = BeginPath&(Out.hdc)
               (绘图)
        dl& = EndPath&(Out.hdc)
     在(绘图)的位置上编写代码来绘出什么图形,就能形成什么样的路径了。不过,并非任何绘图函数都可以产生路径的。可以用来产生路径的函数如下所列。

函数             Windows NT      Windows 95
AngleArc         Yes                     No
Arc                  Yes                     No
ArcTo              Yes                     No
Chord             Yes                     No
Ellipse             Yes                     No
ExtTextOut      Yes                    Yes
LineTo             Yes                    Yes
MoveToEx        Yes                    Yes
Pie                   Yes                     No

 

 

PolyBezier       Yes                    Yes
PolyBezierTo    Yes                    Yes
PolyDraw         Yes                    No

Polygon            Yes                   Yes
Polyline             Yes                   Yes
PolylineTo         Yes                   Yes
PolyPolygon      Yes                   Yes
PolyPolyline       Yes                   Yes
Rectangle          Yes                   No
RoundRect        Yes                   No
TextOut             Yes                  Yes

      看完这个表,我想Windows95的用户就可能有点心痛∶这么多函数用不了!嗨,我也没办法,只好责怪微软了。以下是有关路径的API函数∶

    API 路径函数

函   数                                               说   明
AbortPath                 抛弃选入指定设备场景中的所有路径。也取消目前正在进行的任何路径的创建工作
BeginPath                 启动一个路径分支。在这个命令后执行的GDI绘图命令会自动成为路径的一部分。对线段的连接会结合到一起。设备场景中任何现成的路径都会被清除。参考下表,其中列出的函数都可记录到路径中
CloseFigure               描绘到一个路径时,关闭当前打开的图形(将当前路径段转为闭图)
EndPath                    停止定义一个路径。如执行成功,BeginPath函数调用和这个函数之间发生的所有绘图操作都会正式成为指定设备场景的路径
FillPath                      关闭路径中任何打开的图形,并用当前刷子填充
FlattenPath               将一个路径中的所有曲线都转换成线段
GetPath                    取得对当前路径进行定义的一系列数据
PathToRegion           将当前选定的路径转换到一个区域里
SelectClipPath           将设备场景当前的路径合并到剪切区域里
StrokeAndFillPath      针对指定的设备场景,关闭路径上打开的所有区域。用当前画笔描绘路径的一个轮廓,并用当前刷子填充路径
StrokePath                用当前画笔描绘一个路径的轮廓。打开的图形不会被这个函数



CreateDIBitmap与CreateDIBSection

首先明确最主要区别CreateDIBitmap创建的是 设备相关位图句柄 - HBITMAP.
                               CreateDIBSection创建的是 设备无关位图句柄 - HBITMAP.

DIBDDB之间的相互转换比较慢(关于DIB与DDB区别详见 《设备相关(DDB)与设备无关(DIB)》 ),所以我们使用CreateDIBSection()来创建一个DIB区块。这样作图速度快。 
CreateDIBSection()返回的是一个HBITMAP,CreateDIBitmap()返回的也是HBIT MAP。 
两者的区别在于: CreateDIBSection创建的是一个 DIBSECTION结构,
                             CreateDIBitmap创建的是 BITMAP结构。 
---------------------------------------------------- 
关于DIB区块(DIBSECTION)详见: http://technet.microsoft.com/zh-cn/library/aa930771
可以看到,它包含了一个 位图结构BITMAP,一个DIB信息头BITMAPINFOHEADER,一个掩码表dsBIT fields[3]. 
还有一个内存映射文件句柄和偏移量。我们不去理睬最后两个字段。 
因此,使用GDI函数对 CreateDIBSection()返回的 HBITMAP作图是没有什么问题的。 
 
关于 CreateDIBSection与 CreateDIBitmap函数的对比可参见: http://blog.csdn.net/strikebone/article/details/5832631
 
一、CreateDIBitmap
 
函数功能:该函数由与设备无关的位图(DIB)创建与设备有关的位图(DDB),并且有选择地为位图置位。
所以此函数最终创建的是与设备有关的位图
原型

HBITMAP CreateDIBitmap(HDC hdc,

CONST BITMAPINFOHEADER *lpbmih,

DWORD fdwlnit,

CONST VOID *lpblnit,

CONST BITMAPINFO *lpbmi,

UINT fuUsage);

参数
hdc:设备环境句柄。由于是设备相关的,所以需要指明DC。
lpbmih:指向位图信息头结构的指针,它可以是下列操作系统位图信息头之一:
BITMAPINFOHEADER   Windows NT 3.51和早期使用。
BITMAPV4HEADER       Windows NT 4.0和Windows 95使用。
BITMAPV5HEADER       Windows NT 5.0和Windows 98使用。
如果fdwlnit是CBM_INIT,那么此函数使用此信息 息头结构来获取位图所需的宽度、高度以及其他信息。 注意高度若是正数,那么表示是自底向上DIB,而负数表示为自顶向下DIB,这种情况与CreateDIBitmap函数 兼容。
Fdwlnit:位标识集。它指定系统如何对位图 的位进行初始化。
CBM_INIT:如果设置了该标志,那么系统将使用lpblnit和lpbmi两个参数指向的数据来对位图中的位进行初始化。如果没有该标志,那么表示上述两个参数指向的数据无效。如果fdwlnit为0,那么系统不会对位图 的位进行初始化。
lpblnit:该指针指向包含初始的位图数据的字节类型数组。数据格式与参数lpbmi指向的BITMAPINFO结构中的成员biBitCount有关。
lpbmi:指向BITMAPINFO结构的旨针。该结构描述了参数lpbmi指向的数组的维数和颜色格式。
fuUsage:表示BITMAPINFO结构的成员bmiColors是否初始化过,并且如果是,那么bmiColors是否包含明确的红、绿、蓝(RGB)值或调色板索引。参数fuUsage必须取下列值中的一个,这些值的含义为:
DIB_PAL_COLORS :表示提供一个颜色表,并且该表由该位图要选入的设备环境的逻辑调色板的16位索引值数组组成。
DIB_RGB_COLORS:表示提供一个颜色表,并且表中包含了原义的RGB值。
 
返回值
如果函数执行成功,返回值则是创建的位图的句柄;如果函数执行失败,那么返回值为NULL,若想获取更多错误信息,请调用GetLastError函数。
 
实例:以下代码完成从位图文件中读取DIB位图,并转换成DDB位图:
HBITMAP  LoadBitmapEx( LPCTSTR  lpszFile)
{
if (lpszFile == NULL) return  NULL;
 
 
HBITMAP  hBitmap;
HANDLE  hf;
BITMAPFILEHEADER* pbmfh;
DWORD  dwBytesRead, dwFileSize, dwFileSizeHigh;
BOOL  bSuccess;
// 打开一个bmp文件
hf = CreateFile(lpszFile, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if ( hf == INVALID_HANDLE_VALUE)
{
TRACE( "Open file filed with error %d " , GetLastError());
return  NULL;
}
// 得到这个文件大小
dwFileSize = GetFileSize(hf, &dwFileSizeHigh);
if ( dwFileSizeHigh )
{
CloseHandle(hf);
return  NULL;
}
// 分配内存,大小为该文件的大小
pbmfh = (BITMAPFILEHEADER*) malloc (dwFileSize);
if ( !pbmfh )
{
CloseHandle(hf);
return  NULL;
}
// 读取数据
bSuccess = ReadFile(hf, pbmfh, dwFileSize, &dwBytesRead, NULL);
CloseHandle(hf);
// 效验文件大小和文件格式
if ( !bSuccess || dwFileSize != dwBytesRead || pbmfh->bfType != 0x4D42 || pbmfh->bfSize != dwFileSize)
{
free (( void *)pbmfh);
return  NULL;
}
// 进行DIB转换
hBitmap = CreateDIBitmap(GetWindowDC(NULL),
(BITMAPINFOHEADER*)(pbmfh + 1),
CBM_INIT,
( BYTE *)pbmfh + pbmfh->bfOffBits,
(BITMAPINFO*)(pbmfh + 1),
DIB_RGB_COLORS);
free (( void *)pbmfh);
return  hBitmap;
}

  

其中使用到了 位图信息头结构BITMAPFILEHEADER )及位图信息结构 BITMAPINFO )。
两者的关系详见《 BITMAPFILEHEADER 与 BITMAPINFO 》。
 
再看一个使用CreateDIBitmap函数如何由裸像素数据得到位图句柄 - HBITMAP:
HBITMAP  MakeBitmap( HDC  hDc, LPBYTE  lpBits, long  lWidth, long  lHeight, WORD  wBitCount)
{
BITMAPINFO bitinfo;
memset (&bitinfo, 0, sizeof (BITMAPINFO));
 
bitinfo.bmiHeader.biSize          = sizeof (BITMAPINFOHEADER);
bitinfo.bmiHeader.biWidth         = lWidth;    
bitinfo.bmiHeader.biHeight        = lHeight;  
bitinfo.bmiHeader.biPlanes        = 1; 
bitinfo.bmiHeader.biBitCount      = wBitCount;  
bitinfo.bmiHeader.biCompression   = BI_RGB;  
bitinfo.bmiHeader.biSizeImage     = lWidth*lHeight*(wBitCount/8); 
bitinfo.bmiHeader.biXPelsPerMeter = 96;
bitinfo.bmiHeader.biYPelsPerMeter = 96;  
bitinfo.bmiHeader.biClrUsed       = 0;  
bitinfo.bmiHeader.biClrImportant  = 0;
   
return  CreateDIBitmap(hDc, &bitinfo.bmiHeader, CBM_INIT, lpBits, &bitinfo, DIB_RGB_COLORS);
}
 
一、CreateDIBSection
函数功能该函数创建应用程序可以直接写入的、与设备无关的位图(DIB)。该函数返回一个位图句柄。

 

函数原型    

HBITMAP  CreateDIBSection( HDC                   hdc,
         CONST BITMAPINFO *   pbmi,
         UINT                  iUsage,
         VOID  *               ppvBits,
         HANDLE                hSection,
         DWORD                 dwOffset
 
);

参数   

hdc:设备环境句柄。如果iUsage的值是DIB_PAL_COLORS,那么函数使用该设备环境的逻辑调色板对与设备无关位图的颜色进行初始化。

pbmi:指向BITMAPINFO结构的指针,该结构指定了设备无关位图的各种属性,其中包括位图的尺寸和颜色。

iUsage:指定由pbmi参数指定的BITMAPINFO结构中的成员bmiColors数组包含的数据类型(要么是逻辑调色板索引值,要么是原文的RGB值)。下列值是系统定义的,其含义为:

描述

DIB_RGB_COLORS

根据BITMAPINFOHEADER 的biCompression 成员决定BITMAPINFO 结构包含位掩码还是调色板数组,在呈现位图时使用该数组值。DIB_RGB_COLORS 可以在任何位数的位图上使用。

DIB_PAL_COLORS

BITMAPINFO.bmiColors 数组被取消,在呈现位图时使用目标调色板。DIB_PAL_COLORS只能在8bpp位图中指定。

ppvBits:指向一个变量的指针,该变量接收一个指向DIB位数据值的指针。

hSection:该参数设置为NULL。

dwOffset:参数取消。

返回值

   成功,返回值是一个指向刚刚创建的设备无关位图的句柄,并且*ppvBits指向该位图的位数据值;失败,那么返回值为NULL,并且*ppvBit也为NULL,若想获得更多错误信息,请调用GetLastError函数。

 

备注:

   系统为设备独立位图分配内存。如果在之后调用DeleteObject来删除设备独立位图,系统自动关闭内句柄。

   在Windows CE 2.0及其以后版本,如果图像是调色板模式(通常是1,2,4和8格式)的,BITMAPINFO 结构中必须包含一个颜色表。对于16bpp或32bpp非调色板图像,颜色表必须是3个入口的长度,这3个入口必须指定红、绿、蓝色掩码。 而且,BITMAPINFOHEADER 结构的biCompression 成员应该被设置为BI_BITFIELDS。 这些位深不支持BI_RGB。 GDI取消24bpp图像的颜色表,他们的像素必须被存储为 蓝-绿-红 (BGR)格式。

   Windows CE 不支持332位阈设备。

   在Windows CE 1.0 和 1.01版本,pbmi指向的BITMAPINFO结构必须指定每个像素点为1或2位。

 

实例:下面这段代码实现由已存在的图像裸数据得到CBitmap位图:

CBitmap bitmap ;
BITMAPINFO bmpInfo; //创建位图
bmpInfo.bmiHeader.biSize = sizeof (BITMAPINFOHEADER);
bmpInfo.bmiHeader.biWidth = 480; //宽度
bmpInfo.bmiHeader.biHeight = 480; //高度
bmpInfo.bmiHeader.biPlanes = 1;
bmpInfo.bmiHeader.biBitCount = 24;
bmpInfo.bmiHeader.biCompression = BI_RGB;
 
UINT  uiTotalBytes = 480 * 480 * 3;
void  *pArray = new  BYTE (uiTotalBytes );
HBITMAP  hbmp = CreateDIBSection(NULL, &bmpInfo, DIB_RGB_COLORS, &pArray, NULL, 0); //创建DIB
ASSERT(hbmp != NULL);
//! 将裸数据复制到bitmap关联的像素区域
memcpy (pArray, pImageData, uiTotalBytes);
 
bitmap.Attach(hbmp);
其中 pImageData即是已知的图像裸数据。

注意

 

这里想说声sorry,之前没太深入理解CreateDIBSection函数的工作机理,在上面的例子中使用new为pArray开辟了一块空间,然后将pArray传入CreateDIBSection函数,其实这种方式是错误的,在Debug下面调试之后发现,你使用new开辟的pArray,

 

在使用之后并不能正常delete[]掉。CreateDIBSection参数ppvBits正常的使用应该只要传入一个指针即可(就像上面实例中那样

 

,只要定义一个BYTE *pArray,不需要自己分配空间,直接传给CreateDIBSection即可)。

 

其实通过ppvBits参数类型(二级指针)也就明白了CreateDIBSection函数的基本使用原理 - CreateDIBSection函数使用hDC及

 

BITMAPINFO结构信息创建一个指定大小等信息的位图,系统自动为其开辟所需的像素空间,开辟的像素空间地址就是用ppvBits参数

 

返回的。而且此位图空间系统会自动释放。

 

之所以需要返回此地址,是因为CreateDIBSection调用时只是根据我们提供的一些信息创建合适大小、格式的位图并开辟控件

 

,而并不会填充实际像素值(这也是为什么我们直接将指向真实像素值的指针传入此函数中无效的原因),返回这个指针只是为了

 

之后使用真实像素值填充此段空间(就像上面示例中的memcpy那样)!

 

 

1、关于CreateDIBSection函数的使用需要注意一点就是参数ppvBits的使用:传给CreateDIBSection函数的不是一个new

 

来一块的空间,也不是直接传入真实像素指针pImageData,而只是传入一个指针变量(如本例中的pArray)用以接收CreateDIBSection自动开辟的像素区域指针!如果你传入了自己手动new出来的一块区域,在Debug调试运行时会发现一个delete []错误,此错误指明你之前new出来的空间无法释放,原因是堆损坏,这部分需要了解下new、delete(或内存检测)原理,详见《new/delete内存分配及释放检测》。

 

2、像素数据的复制必须在调用了CreateDIBSection函数之后进行,如本例所示,memcpy(pArray, pImageData, uiTotalBytes);是在HBITMAP hbmp = CreateDIBSection(NULL, &bmpInfo, DIB_RGB_COLORS, &pArray, NULL, 0);之后进行的。可能是由于CreateDIBSection函数会对传入的像素空间进行“清除”工作,所以在调用此函数前就先进行像素复制的话,你会发现最终绘制的图像时一片漆黑!

 

说明:CreateDIBSection函数具有内存映射文件功能,但是一般我们不会使用!所以此处忽略讨论!

 

此外还需说明的是,有时候在使用CreateDIBSection函数时会发生内存泄露的问题。当初本人开发一个项目时曾经碰到过此问题,在一个窗口进入特定模式后会频繁的调用此函数生成特定的图像,并用此图像去重绘窗口背景。最后调试正是此函数发生了内存泄露,但是代码中确是对产生的HBITMAP及DC进行了释放。当时是使用了另一种方式解决了此问题,但是对于CreateDIBSection函数发生的泄露问题却一直没有深入研究。

 

虽然如此,对于使用此函数之后善后处理这里仅提几点:

1、最后一次使用完CreateDIBSection函数创建位图之后将其从DC中换出memdc.SelectObject(pOldBmp);

2、在最后一次使用完CreateDIBSection函数创建的位图之后才可delete[] pArray释放掉像素空间。

3、注意释放位图资源bitmap.DeleteObject();

4、释放内存memdc.ReleaseDC();


  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值