利用GDI+快速获取图像数据及处理中需注意的问题

  

       示例工程:https://files.cnblogs.com/laviewpbt/LockBits.rar

 

GDI+是对GDI的很大程度上的改进,其设计理念以及操作的方式和GDI相比有了本质的不同,因其优秀的抗锯齿功能以丰富接口函数深受广大图像和图形编程者的爱好,同GDI相比,在编程上有着较大的不同。

GDI+的一些列函数中,有两个比较特殊,他们是GdiplusStartup以及GdiplusShutdown,在执行所有的GDI+图形或图像的函数前,一定要先调用GdiplusStartup函数,否则将返回GdiplusNotInitialized的错误,在程序关闭之前一定要调用GdiplusShutdown,否则如果你是处于VBIDE中,则不定时的会出现VBIDE需要关闭的错误,此时,即使你不管他,一旦在VB中有输入中文或复制等动作时,VB就会出现假死,VB中的任何按钮都不可点击。

GDI+对图像格式的支持相对于GDI有了很大的扩展,在VB6.0中,不支持透明图像,比如Png格式,这个是有历史原因的,在VB6.0发行的时候,Png格式并未获得流行。GDI+可打开的格式有BMPJPEGGIFPNGTIFF 等,其中GIFTIFF可包含多帧图像。同样,GDI+亦支持对上述格式的保存,但是不能保存多帧的GIF,这和GIF的版权可能有关。

VB中调用GDI+也是一件简单的事情,网络上已经有完整的平板化GDI+API下载,无法找到链接或者需要的可以直接和我联系。

好了,废话少说,下文我们简单谈谈如何利用GDI+获取图像的数据。

首先,我们需要做的是打开一副图像,这个在GDI+中有个函数GdipLoadImageFromFile,这个函数我们经常看到有两个版本的声明,分别为:

Private Declare Function GdipLoadImageFromFile Lib "gdiplus" (ByVal FileName As String, Image As Long) As Long

Private Declare Function GdipLoadImageFromFile Lib "gdiplus" (ByVal FileName As Long, hImage As Long) As Long

其中第一个版本的FileName不能直接用我们的路径,而是用StrConv(FileName, vbUnicode),否则调用会失败,第二个版本则用StrPtrFileName)。

如果我们调用成功,则Image的值不为0

如果image不为0,则说明我们调用成功,此时如果需要显示他,则可以直接调用GdipDrawImage或其其他重载函数。

为了得到图像数据以便我们进一步处理,我们需要寻找合适的函数,GDI+中同样有类似于GDIGetPixel函数,即GdipBitmapGetPixel,同样这个不适合于图像处理,接着我们在GDI+的平板化API中寻找了GdipBitmapLockBits/ GdipBitmapUnlockBits这样一对函数,其声明如下:

Public Declare Function GdipBitmapLockBits Lib "gdiplus" (ByVal bitmap As Long, rect As RECTL, ByVal flags As ImageLockMode, ByVal PixelFormat As Long, lockedBitmapData As BitmapData) As long

Public Declare Function GdipBitmapUnlockBits Lib "gdiplus" (ByVal bitmap As Long, lockedBitmapData As BitmapData) As long

      我们先看看这个BitmapData结构。

Public Type BitmapData

   Width As Long

   Height As Long

   stride As Long

   PixelFormat As Long

   scan0 As Long

   Reserved As Long

End Type

查看MSDN,可以知道WidthHeight分别表示图像的宽度和高度,stride表示图像的扫描行宽度,PixelFormat表示图像格式,而这个scan0则表示图像的内存地址。这个scan0真是个令人欢喜的东西令人忧的东西啊,欢喜的是我们找到图像的数据了,忧的而是他是个指针,而VB没有指针。在我很久前发布的一篇文章vb.net中彩色图像数据的快速获取中所采用的方法是用Marshal.Copy把数据拷贝到一个临时数组中,那是因为在VB.NET真的无法用指针,并且使用这种方法一会造成速度缓慢,因为图像数据处理完后又要拷贝回去,二是占用的内存要多一倍。令人欣慰的是,在VB6.0中,我们可以用模拟指针的方法间接实现直接访问内存。具体请参考 图像处理系列教程之三:VB中的指针。

在调用GdipBitmapLockBits后,如果调用成功,则会将相关信息填充到lockedBitmapData结构体中,类似于GetDIBits函数,该函数也可以设置要获取的格式,即PixelFormat参数,但是,由于GDI+的出发点不同,他会动态的加载Image,即调用GdipLoadImageFromFile函数并没有为图像分配对应的内存,因此我们强烈建议这里的PixelFormat设置成和图像实际的格式一致,这个格式可以通过GdipGetImagePixelFormat获得。

至于参数中的RECTL,则表示要获取数据的矩形区域,如果你只需获取部分数据,则可以在这里改变,一般我们都需要获取图像的整个数据,这里有几个函数可以使用,如下:

GdipGetImageWidth/ GdipGetImageHeight 

或者GdipGetImageDimension

在处理完图像的数据后,类似于SetDibits,我们要调用GdipBitmapUnlockBits来更新图像。

       当然,如果图像是索引图像或者单色位图,我们对其进行处理时并不关心他的每个像素点的数据,而更关心其调色板,实际上,PS中对索引图像的一系列处理也只是调整了他的调色板。那么判断一副图像是否是索引图像或者单色图像最简单的方式就是利用上述GdipGetImagePixelFormat返回的值,如果返回值是PixelFormat8bppIndexed, PixelFormat4bppIndexed, PixelFormat1bppIndexed,则为索引图像,但是有一点要注意灰度图像是一种特殊的索引图像,由于其调色板的特殊性,灰度图像的处理是不可以直接改变其调色板的,而是改变其图像数据,这里我们不做介绍。

当我们知道所处理的对象是索引图像时,则可以先通过GdipGetImagePaletteSize函数得到其调色板的大小,然后利用GdipGetImagePalette来填充我们所定义的调色板,由于VB中结构体的某些内存分配方面的限制,为编程统一方便,GDI+中的调色板在VB中需要按如下方式定义:

Private Type ColorPalette '(8bpp)

   flags        As PaletteFlags

   Count        As Long

   Entries(255) As RGBQUAD

End Type

这样,如果图像没有256个调色板值,也没有关系,我们可以通过GdipGetImagePaletteSize所确定的值做适当处理,同时由上式也可以看到,公式(PaletteSize-8/4即为我们的调色板中实际用到的RGBQUAD结构体个数。之后,把我们的图像算法应用到这个调色板后,在通过GdipSetImagePalette更新图像的调色板则完成了整个处理过程。

另外,在GDI+中除了可以利用GdipLoadImageFromFile从文件中创建GDI+Image对象,还可利用GdipCreateBitmapFromGdiDibGDIDIB对象、利用GdipCreateBitmapFromHBITMAPStdPicture对象,利用GdipCreateBitmapFromHICONIcon的句柄,利用GdipCreateBitmapFromScan0从位图在内存的首地址等等对象创建Image,因此可方便的将GDI的图形对象转换到GDI+中。

依旧以反色为例,则GDI+版本的程序可以这样写:


'***************************GDI+版反色*********************************
'**  作者          :    laviewpbt
'**  开发时间      :    2009.4.1
'**  最后修改时间  :      2009.5.31
'**  联系方式      :      QQ:33184777
'**  E-MAIL      :      laviewpbt@sina.com
'**  All Rights Resered
'***********************************************************************

Private Function Invert(Image As Long) As Boolean

    Dim PixelFormat         As Long

    Dim Dimensions          As RECTF, Rct               As RECT

    Dim BmpData             As BitmapData, Rtn          As Long

    Dim DataArr(0 To 3)     As Byte, pDataArr(0 To 0)   As Long

    Dim OldArrPtr           As Long, OldpArrPtr         As Long

    Dim LineAddBytes        As Long, PixelAddBytes      As Long

    Dim X                   As Long, Y                  As Long

    GdipGetImageBounds Image, Dimensions, UnitPixel                   ' 得到图像的大小,以像素为单位,这个函数理论上不会不成功

    GdipGetImagePixelFormat Image, PixelFormat                        ' 得到图像的真正格式

    Select Case PixelFormat

    Case PixelFormat32bppRGB, PixelFormat24bppRGB

        Rct.Right = Dimensions.nWidth

        Rct.Bottom = Dimensions.nHeight

        GdipBitmapLockBits Image, Rct, ImageLockModeRead, PixelFormat, BmpData    '读取图像的数据

        MakePoint VarPtrArray(DataArr), VarPtrArray(pDataArr), OldArrPtr, OldpArrPtr    '模拟指针

        pDataArr(0) = BmpData.scan0                                                     '指向图像在内存中的首地址

        PixelAddBytes = IIf(PixelFormat = PixelFormat32bppRGB, 4, 3)                    '每个像素所占用的字节数

        LineAddBytes = BmpData.Stride - BmpData.Width * PixelAddBytes                   '每个扫描行中多余的字节数,不需要处理的

        For Y = 1 To BmpData.Height                                                     '从上到下扫描

            For X = 1 To BmpData.Width                                                  '从左到右扫描

                DataArr(0) = 255 - DataArr(0)                                           '具体的算法

                DataArr(1) = 255 - DataArr(1)

                DataArr(2) = 255 - DataArr(2)

                pDataArr(0) = pDataArr(0) + PixelAddBytes                               '指针移位

            Next

            pDataArr(0) = pDataArr(0) + LineAddBytes                                    '一到下一个扫描行的起始位置

        Next

        FreePoint VarPtrArray(DataArr), VarPtrArray(pDataArr), OldArrPtr, OldpArrPtr    '释放模拟指针

        GdipBitmapUnlockBits Image, BmpData                                               '更新数据

    Case PixelFormat8bppIndexed, PixelFormat4bppIndexed, PixelFormat1bppIndexed

        Dim Palette         As ColorPalette

        Dim PaletteSize     As Long

        GdipGetImagePaletteSize Image, PaletteSize

        GdipGetImagePalette Image, Palette, PaletteSize

        For Y = 0 To (PaletteSize - 8) / 4 - 1

            Palette.Entries(Y).Blue = 255 - Palette.Entries(Y).Blue

            Palette.Entries(Y).Green = 255 - Palette.Entries(Y).Green

            Palette.Entries(Y).Red = 255 - Palette.Entries(Y).Red

        Next

        GdipSetImagePalette Image, Palette

    End Select

End Function

GDI+中没有DC属性,取而代之的是Graphics对象,要绘制图像,必须先创建GraphicsGdipCreateFromHDC函数可以将DC转换为Graphics,然后调用绘图函数绘制后一定要记得GdipDeleteGraphics已删除。

同样,当不在使用Image对象时,也需要调用GdipDisposeImage释放他。

 

'******************************你的评论是我发表文章的极大动力**'**************************

'***************************欢迎和你讨论图像技术问题:QQ 33184777************************

 

 

posted on 2009-05-31 20:44  彭佳乐  阅读(9443)  评论(2编辑  收藏  举报

导航