首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 开发语言 > VB >

这几天老是有人问小弟我图像获取的有关问题,其实这个有关问题很多人都回答过,小弟我也总结过,可能没有写在CSDN的博客下,这里在转发一上,希望大家不要在为这个有关

2012-12-16 
这几天老是有人问我图像获取的问题,其实这个问题很多人都回答过,我也总结过,可能没有写在CSDN的博客上,这

这几天老是有人问我图像获取的问题,其实这个问题很多人都回答过,我也总结过,可能没有写在CSDN的博客上,这里在转发一下,希望大家不要在为这个问题加我了。
本帖最后由 laviewpbt 于 2010-05-27 22:32:57 编辑 我在博客园上发表过的,可能那里用VB的人不多

http://www.cnblogs.com/laviewpbt/archive/2009/05/31/1493222.html
http://www.cnblogs.com/laviewpbt/archive/2009/05/31/1492878.html


一:利用GDI快速获取图像数据及处理中需注意的问题
示例工程:http://files.cnblogs.com/laviewpbt/GetDibits.rar

说明:本文中大部分资料亦适合于VC或其他编程语言。

谈起VB中的图像,很多人觉得VB对图像的支持太弱了,实际上,我觉得,比起VC,VB对图像的编程要方便很多。神奇的AutoRedraw,多格式的图像文件支持,AutoSize让你少去不少麻烦等等,而这些在VC中都是需要不少额外的代码的,并且VB内部用底层函数对这些功能的封装,使得其执行效率亦是相当高的。那么,今天,我要给大家用一个简单的反色程序说明如何用VB实现对各种色深的图片的处理,让你对VB信心十足。

一、打开图像。

这个很简单,直接使用 LoadPicture(其实就是对OleLoadPicture这个函数的封装),其支持BMP,JPG,GIF,ICO,WMF,EMF格式。这里说明一下,对于由柯达控件生成的BMP,部分GIF和JPEG2000,以及32位的ICO这个函数似乎会产生一些未知的错误。JPG格式加载后,无论原始的JPG是否是8位的,在内存中VB是以24位的DIB格式图像保存的,而GIF则以8位索引色放置于内存。至于BMP格式,则可以按照原始的文件位数加载。我们可以用下面的函数来认证。

Private Function GetBitmapColorDepth(Pic As StdPicture) As Long
        Dim Bmp As Bitmap
        GetGDIObject Pic.Handle, Len(Bmp), Bmp
        GetBitmapColorDepth = Bmp.bmBitsPixel
    End Function

二、保存图像。

这个也很简单,SavePicture函数,注意,对于BMP、ICO、WMF、EMF格式,SavePicture函数能以原始格式保存,而对于加载的GIF和 JPG格式,只能将图像保存为BMP格式,无论你给他的路径参数的后缀是什么(看到有些VB的书上居然说将后缀改为JPG就能保存为JPG格式,真是傻)。并且该函数能保留原始的位深,这对我们来说是个好消息。那么这个函数的实现在我看来也很简单,用VB内部的语言来表达就是:
    Put #FileNum, , BmpInfoHeader          'BMP文件头
    Put #FileNum, , mBmpInfo                '位图信息头
    If mBmpInfo.biBitCount <= 8 Then Put #FileNum, , ColorTable  '调色板
    Put #FileNum, , DibBytes                '位图数据

三、图像数据的获得

这个地方就是大家常常说VB慢的罪魁祸首,因为VB的自带了一个四不像的Point和Pset函数,而这两个函数可以得到和设置图像的颜色,因此,常常会作为初学者的最爱工具,而最终的结果就是让VB落得一个骂名:龟速。 

总结一下,在 VB中可以用来得到图像数据的常用API函数有:Point|Pset; 

GetPixel|SetPixel;  GetBitmapBits|SetBitmapBits; GetDIBits|SetDIBits ; SafeArray模拟指针等等。抛弃前两组不说,因为他们是一丘之貉。第三组函数因为是DDB函数,是设备相关的,个人认为不是很好,因为我体验过他莫名其妙的失败。最后一组因为其复杂性,不作为向大家推荐的函数。因此,我们重点谈谈 GetDIBits。
    Private Declare Function GetDIBits Lib "gdi32" Alias "GetDIBits" (ByVal aHDC As Long, ByVal hBitmap As Long, ByVal nStartScan As Long, ByVal nNumScans As Long, lpBits As Any, lpBI As BITMAPINFO, ByVal wUsage As Long) As Long

谈起GetDIBits,我们重点讲讲参数lpBits,这个参数表示存储图像数据的缓冲区首地址,编程时只需要将一个数组的第一个元素赋值给他就可以了, 而如何确定这个数组的大小是值得商榷的。我们知道,对于不同的位深每个像素所占用的字节数是不同的,既然VB保留了被加载的图像的位深,那么我们在对图像进行后续处理的时候就应该按照这个位深来给图像数据缓冲区分配内存。不过,也许大家在实际的应用中并没有这样做,而是统一把 BITMAPINFO.bmiHeader.biBitCount设置为32或者为24,那么这里其实GetDIBits 帮我们帮图像的原始格式的数据转换为我们所需要的数据了。 
    

好,下面给出一个简单的处理反色的函数。


[最优解释]
顶顶顶~
[其他解释]


Option Explicit
Private Type BITMAPFILEHEADER
    bfType      As Integer
    bfSize      As Long
    bfReserved1 As Integer
    bfReserved2 As Integer


    bfOffBits  As Long
End Type

Private Type Bitmap
  bmType As Long
  bmWidth As Long
  bmHeight As Long
  bmWidthBytes As Long
  bmPlanes As Integer
  bmBitsPixel As Integer
  bmBits As Long
End Type

Private Type BITMAPINFOHEADER      '40 bytes
  biSize As Long                  'BITMAPINFOHEADER结构的大小
  biWidth As Long
  biHeight As Long
  biPlanes As Integer              '设备的为平面数,现在都是1
  biBitCount As Integer            '图像的颜色位图
  biCompression As Long            '压缩方式
  biSizeImage As Long              '实际的位图数据所占字节
  biXPelsPerMeter As Long          '目标设备的水平分辨率
  biYPelsPerMeter As Long          '目标设备的垂直分辨率
  biClrUsed As Long                '使用的颜色数
  biClrImportant As Long          '重要的颜色数。如果该项为0,表示所有颜色都是重要的
End Type
 
Private Type RGBQUAD                '只有bibitcount为1,2,4时才有调色板
    Blue As Byte                    '蓝色分量
    Green As Byte                    '绿色分量
    Red As Byte                      '红色分量
    Reserved As Byte                '保留值
End Type

Private Type BITMAPINFO
    bmiHeader As BITMAPINFOHEADER
    bmiColors As RGBQUAD
End Type

Private Const BI_RGB = 0&
Private Const DIB_RGB_COLORS = 0&
Private Declare Function GetDIBits Lib "gdi32" (ByVal aHDC As Long, ByVal hBitmap As Long, ByVal nStartScan As Long, ByVal nNumScans As Long, lpBits As Any, lpBI As BITMAPINFO, ByVal wUsage As Long) As Long
Private Declare Function GetGDIObject Lib "gdi32.dll" Alias "GetObjectA" (ByVal hObject As Long, ByVal nCount As Long, ByRef lpObject As Any) As Long
Private Declare Function SetDIBits Lib "gdi32" (ByVal hDC As Long, ByVal hBitmap As Long, ByVal nStartScan As Long, ByVal nNumScans As Long, lpBits As Any, lpBI As BITMAPINFO, ByVal wUsage As Long) As Long


Private Declare Function GetDC Lib "user32" (ByVal hwnd As Long) As Long
Private Declare Function ReleaseDC Lib "user32" (ByVal hwnd As Long, ByVal hDC As Long) As Long


Private Function Invert(Pic As StdPicture) As Boolean  '
    Dim i          As Long, hDC    As Long
    Dim Bmp As Bitmap, BmpInfo As BITMAPINFO
    GetGDIObject Pic.Handle, Len(Bmp), Bmp
    With BmpInfo.bmiHeader
        .biSize = Len(BmpInfo.bmiHeader)
        .biWidth = Bmp.bmWidth
        .biHeight = Bmp.bmHeight
        .biPlanes = 1
        .biBitCount = Bmp.bmBitsPixel              '
按图像实际的位深设置 , 
        .biCompression = BI_RGB
    End With
    hDC = GetDC(0)
    ReDim PicData(Bmp.bmWidthBytes * Bmp.bmHeight - 1) As Byte    '按图像数据实际的大小分配缓冲区
    GetDIBits hDC, Pic.Handle, 0, Bmp.bmHeight, PicData(0), BmpInfo, DIB_RGB_COLORS
    For i = 0 To UBound(PicData)
        PicData(i) = 255 - PicData(i)
    Next
    SetDIBits hDC, Pic.Handle, 0, Bmp.bmHeight, PicData(0), BmpInfo, DIB_RGB_COLORS
    ReleaseDC 0, hDC
End Function 

调用方式类似于如下: Call Invert(Picture1.Picture)然后刷新一下: Picture1.Refresh. 

注意,VB中的Image对象的位深是和屏幕色深一致的,而 picture对象才会保留原始文件的位深,因此测试一定要用Picture1.Picture,不然就失去了意义。
以上假设你加载的是一副24 位的图像(不管原始是什么格式).   

在以上函数中,语句.biBitCount = Bmp.bmBitsPixel  表示按照实际的位深来读取数据,而 ReDim PicData(Bmp.bmWidthBytes * Bmp.bmHeight - 1) As Byte 则表示按图像数据实际的大小分配缓冲区 ,这时的GetDIBits 我们可以理解为一个copymemory的过程。如果你把GetDIBits 那句改为    CopyMemory PicData(0), ByVal Bmp.bmBits, Bmp.bmWidthBytes * Bmp.bmHeight,得到的效果是一样的(感觉和模拟指针有点联系),其中 Bmp.bmBits实际上就是图像在内存的首地址,类似的SetDIBits 也可以用CopyMemory 来代替。
    值得说明的一点是,对于这个PicData数组的类型,不同的人可以有不同的爱好,如果.biBitCount 设置为8位及其以下位深的图像,我们没有理由将其声明为Byte之外的任何类型,若biBitCount 设置为24或 32,可能有很多人喜欢或习惯将PicData声明为RGBQUAD 结构类型,以方便理解每个分量的意义,这也无可非议,但是要注意在设置为24位的时候要删除RGBQUAD的Reserved分量声明,否则数组中的数据是不对的。

另外,还有一点,对图像数据很多人喜欢用二维数据或者三维数组来记录,这样做的目的无非是数组的意义明确,我们不推荐你这样做,原因是二维数组的寻址是有系统自动完成的,在实际的操作中,有着大量的重复寻址的操作,系统确无法在这个过程中实施优化,如果是一维数组,这个操作就完全由我们掌握。

若你决定用二维或三维的数据来保存图像的数据,你很可能会在编码的过程中得到不正确的结果,这是因为你对VB的数组在内存中的保存顺序不了解。对于VB中的二维数组,比如 Data(3,5),在内存中,其摆放的顺序是Data(0,0), Data(1,0), Data(2,0), Data(3,0), Data(0,1),Data(1,1),Data(2,1),Data(3,1)……,不是我们常见的矩阵从左到右,然后在从上到下,而图像的数据是按照从上到下一个扫描行一个扫描行依次保存的。因此,你所用的二维数组的第一维必须是一个扫描行的大小。

简单的总结下可用的数组声明方式。

PicData(Bmp.bmWidthBytes * Bmp.bmHeight - 1) As Byte  ‘通用型

PicData(Bmp.bmWidthBytes-1 , Bmp.bmHeight - 1) As Byte  ‘通用型



PicData(Bmp.bmWidth-1, Bmp.bmHeight - 1) As RGBQUAD ‘只对biBitCount=32有效

PicData(3,Bmp.bmWidth-1, Bmp.bmHeight - 1) As Byte  ‘只对biBitCount=32有效

PicData(Bmp.bmWidth-1, Bmp.bmHeight - 1) As long‘只对biBitCount=32有效

注意:请按照上面数组的位数设置GetDIBits的lpBits参数。

以上的反色代码假设你加载的是一副24位或32位的图像,但是如果你加载一副8位或8位以下的BMP图像,然后执行该代码看看(提示,一定要保存下先哦),怎么样,VB悄无声息的消失了,这一次,你问10个人有9个人可能不知道问题出在那里,似乎每个函数都没有问题,如果你单步调试,发现执行到GetDIBits 这里VB挂掉,因此,问题就出在这个函数上。
    我们知道,8位及8位以下的图像都有调色板,那么调色板的信息如何得到呢,我们注意到
Private Type BITMAPINFO
    bmiHeader As BITMAPINFOHEADER
    bmiColors As RGBQUAD
End Type
    这个结构中除了位图信息头之外还有个RGBQUAD 元素,GetDIBits 函数在执行时会自动将图像的信息填充到这个结构体中,而对于8位位图,一般有256个RGBQUAD 元素的调色板,而我们的声明中只给了他一个元素的空间,因此,会造成访问非法内存之类的事情发生,导致IDE崩溃。
    那么,解决问题的方式就是修改BITMAPINFO 结构的声明方式,现修改如下:
    Private Type BITMAPINFO
        bmiHeader As BITMAPINFOHEADER
        bmiColors(255) As RGBQUAD
    End Type
    然后执行类似的代码,很多情况下,你看到的不正确的效果,至于为什么,详细的分析见  http://topic.csdn.net/u/20070505/16/3bb480ae-3eeb-4f5d-80db-19ecc61202e6.html
    实际上,在PS中能对索引色进行调增的选项很少,只有调整菜单中的若干项,而那若干项其实都是读调色板进行处理的,而没有改变实际的图像数据,因此,对于 8位色以下的图像,反色的过程应该类似如下:
    For i = 0 To 255
        BmpInfo.bmiColors(i).Red = 255 - BmpInfo.bmiColors(i).Red
        BmpInfo.bmiColors(i).Green = 255 - BmpInfo.bmiColors(i).Green
        BmpInfo.bmiColors(i).Blue = 255 - BmpInfo.bmiColors(i).Blue
    Next 
    对于4位色以及1为色,调色板中最多只会有16和2个元素,对于8位色,也会存在调色板中只有在【17,255】个元素的情况,但是由于对索引色图像的处理时一般不改动数据,而只改变调色板,为了方便,这里的 bmiColors(255) As RGBQUAD 直接定义为255个比较方便。
    按照严格的定义,索引色中实际使用的调色板数应该由biClrUsed As Long给出,但是,我观察过很多图像,这个值默认都是0,即使8位色没有使用256个或4位色没有使用哪个16个调色板,因此,如果你必须得到实际用的调色板数,可以根据bmiColors的颜色数字来判断。提示:PS中会把白色和黑色两种索引色放在调色板的最前面(如果有的话)。
    好了,也许你认为完美了,其实不然,我们再回到24色及32位色的问题上, 
    For i = 0 To UBound(PicData)
        PicData(i) = 255 - PicData(i)
    Next
    从严格的以上讲,这个代码所执行的过程已经大于了反色算法的需求了,这里因为是简单反色算法,所以我们看不出什么异常。而实际过程是,我们很有可能处理了一些我们不需要处理的数据。还记得扫描行的概念吗,扫描行的字节数必须是4的倍数(这是指DIB,对于DDB是2个倍数,一定要分清哦),不够的部分用0 补齐。由我们上述的代码可以看到,对于这些用0补齐的部分反色后就变为255了,因此,最终的反色算法应该如下:

Public Function Invert(Pic As StdPicture) As Boolean  '
    Dim i          As Long, j          As Long
    Dim hDC        As Long, Speed      As Long
    Dim Pixel      As Long
    Dim Bmp        As Bitmap, BmpInfo  As BITMAPINFO
    GetGDIObject Pic.Handle, Len(Bmp), Bmp
    With BmpInfo.bmiHeader
        .biSize = Len(BmpInfo.bmiHeader)
        .biWidth = Bmp.bmWidth


        .biHeight = Bmp.bmHeight
        .biPlanes = 1
        .biBitCount = Bmp.bmBitsPixel
        .biCompression = BI_RGB
    End With
    hDC = GetDC(0)
    ReDim PicData(Bmp.bmWidthBytes * Bmp.bmHeight - 1) As Byte
    GetDIBits hDC, Pic.Handle, 0, Bmp.bmHeight, PicData(0), BmpInfo, DIB_RGB_COLORS
    If Bmp.bmBitsPixel <= 8 Then
        For i = 0 To 255
            BmpInfo.bmiColors(i).Red = 255 - BmpInfo.bmiColors(i).Red
            BmpInfo.bmiColors(i).Green = 255 - BmpInfo.bmiColors(i).Green
            BmpInfo.bmiColors(i).Blue = 255 - BmpInfo.bmiColors(i).Blue
        Next
    Else
        Pixel = Bmp.bmBitsPixel " 8
        For j = 0 To Bmp.bmHeight - 1
            Speed = j * Bmp.bmWidthBytes
            For i = 0 To Bmp.bmWidth - 1
                PicData(Speed) = 255 - PicData(Speed)          'Blue
                PicData(Speed + 1) = 255 - PicData(Speed + 1)  'Green
                PicData(Speed + 2) = 255 - PicData(Speed + 2)  'Red
                Speed = Speed + Pixel                          '这里这样写是标准的过程,而没有考虑优化
            Next
        Next
    End If
    SetDIBits hDC, Pic.Handle, 0, Bmp.bmHeight, PicData(0), BmpInfo, DIB_RGB_COLORS
    ReleaseDC 0, hDC
End Function
    讲了一大堆,其实过程很简单,但是要注意到这些细节,还是有些学问的,这里拿反色只是举个例子,大家可以举一反三。

顺便谈一下,上述过程和模拟指针有多大区别啊,上面不是谈到如果按照实际的位数调用哪个GetDIBits 函数,就可以看成一个CopyMemory的过程吗,用模拟指针实际上就是不要这个CopyMemory的过程了,而直接访问图像内存中的地址。
Public Function Invert(Pic As StdPicture) As Boolean  '
    Dim i          As Long, j          As Long
    Dim hDC        As Long, Speed      As Long
    Dim Pixel      As Long
    Dim Bmp        As Bitmap, BmpInfo  As BITMAPINFO


    GetGDIObject Pic.Handle, Len(Bmp), Bmp
    With BmpInfo.bmiHeader
        .biSize = Len(BmpInfo.bmiHeader)
        .biWidth = Bmp.bmWidth
        .biHeight = Bmp.bmHeight
        .biPlanes = 1
        .biBitCount = Bmp.bmBitsPixel
        .biCompression = BI_RGB
    End With
    hDC = GetDC(0)
    ReDim PicData(Bmp.bmWidthBytes * Bmp.bmHeight - 1) As Byte
    GetDIBits hDC, Pic.Handle, 0, Bmp.bmHeight, PicData(0), BmpInfo, DIB_RGB_COLORS
    If Bmp.bmBitsPixel <= 8 Then
        For i = 0 To 255
            BmpInfo.bmiColors(i).Red = 255 - BmpInfo.bmiColors(i).Red
            BmpInfo.bmiColors(i).Green = 255 - BmpInfo.bmiColors(i).Green
            BmpInfo.bmiColors(i).Blue = 255 - BmpInfo.bmiColors(i).Blue
        Next
    Else
        Pixel = Bmp.bmBitsPixel " 8
        For j = 0 To Bmp.bmHeight - 1
            Speed = j * Bmp.bmWidthBytes
            For i = 0 To Bmp.bmWidth - 1
                PicData(Speed) = 255 - PicData(Speed)          'Blue
                PicData(Speed + 1) = 255 - PicData(Speed + 1)  'Green
                PicData(Speed + 2) = 255 - PicData(Speed + 2)  'Red
                Speed = Speed + Pixel                          '这里这样写是标准的过程,而没有考虑优化
            Next
        Next
    End If
    SetDIBits hDC, Pic.Handle, 0, Bmp.bmHeight, PicData(0), BmpInfo, DIB_RGB_COLORS
    ReleaseDC 0, hDC
End Function
    讲了一大堆,其实过程很简单,但是要注意到这些细节,还是有些学问的,这里拿反色只是举个例子,大家可以举一反三。

顺便谈一下,上述过程和模拟指针有多大区别啊,上面不是谈到如果按照实际的位数调用哪个GetDIBits 函数,就可以看成一个CopyMemory的过程吗,用模拟指针实际上就是不要这个CopyMemory的过程了,而直接访问图像内存中的地址。
[其他解释]
二: 利用GDI+快速获取图像数据及处理中需注意的问题

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

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



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

GDI+对图像格式的支持相对于GDI有了很大的扩展,在VB6.0中,不支持透明图像,比如Png格式,这个是有历史原因的,在VB6.0发行的时候,Png格式并未获得流行。GDI+可打开的格式有BMP、JPEG、GIF、PNG、TIFF 等,其中GIF、TIFF可包含多帧图像。同样,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),否则调用会失败,第二个版本则用StrPtr(FileName)。

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

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

为了得到图像数据以便我们进一步处理,我们需要寻找合适的函数,GDI+中同样有类似于GDI的GetPixel函数,即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,可以知道Width、Height分别表示图像的宽度和高度,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对象,还可利用GdipCreateBitmapFromGdiDib从GDI的DIB对象、利用GdipCreateBitmapFromHBITMAP从StdPicture对象,利用GdipCreateBitmapFromHICON从Icon的句柄,利用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对象,要绘制图像,必须先创建Graphics,GdipCreateFromHDC函数可以将DC转换为Graphics,然后调用绘图函数绘制后一定要记得GdipDeleteGraphics已删除。

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

 

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

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

 
[其他解释]
二: 利用GDI+快速获取图像数据及处理中需注意的问题

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

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

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

GDI+对图像格式的支持相对于GDI有了很大的扩展,在VB6.0中,不支持透明图像,比如Png格式,这个是有历史原因的,在VB6.0发行的时候,Png格式并未获得流行。GDI+可打开的格式有BMP、JPEG、GIF、PNG、TIFF 等,其中GIF、TIFF可包含多帧图像。同样,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),否则调用会失败,第二个版本则用StrPtr(FileName)。

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

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

为了得到图像数据以便我们进一步处理,我们需要寻找合适的函数,GDI+中同样有类似于GDI的GetPixel函数,即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,可以知道Width、Height分别表示图像的宽度和高度,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对象,还可利用GdipCreateBitmapFromGdiDib从GDI的DIB对象、利用GdipCreateBitmapFromHBITMAP从StdPicture对象,利用GdipCreateBitmapFromHICON从Icon的句柄,利用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对象,要绘制图像,必须先创建Graphics,GdipCreateFromHDC函数可以将DC转换为Graphics,然后调用绘图函数绘制后一定要记得GdipDeleteGraphics已删除。

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

 

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

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

 
[其他解释]
帮你断一下
[其他解释]
顶顶,我昨天还引用了你写的一个图片保存代码。
[其他解释]
好  ,,顶


[其他解释]
不顶对不起国家啊!
[其他解释]
楼主乃达人!佩服。
[其他解释]

引用:
顶顶,我昨天还引用了你写的一个图片保存代码。


那个代码有一些BUG的,我一直没有去更正他。
[其他解释]
null

热点排行