本帖最后由 laviewpbt 于 2010-05-27 22:32:57 编辑

解决方案 »

  1.   

    Option Explicit
    Private Type BITMAPFILEHEADER
        bfType      As Integer
        bfSize      As Long
        bfReserved1 As Integer
        bfReserved2 As Integer
        bfOffBits  As Long
    End TypePrivate Type Bitmap
      bmType As Long
      bmWidth As Long
      bmHeight As Long
      bmWidthBytes As Long
      bmPlanes As Integer
      bmBitsPixel As Integer
      bmBits As Long
    End TypePrivate 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 TypePrivate Type BITMAPINFO
        bmiHeader As BITMAPINFOHEADER
        bmiColors As RGBQUAD
    End TypePrivate 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的过程了,而直接访问图像内存中的地址。
      

  2.   


    那个代码有一些BUG的,我一直没有去更正他。
      

  3.   

    顶,一直没想学GDI+,LZ写的真不错,算是入门了