vb.net不能直接操作指针,只能用marshal类来进行托管内存和非托管内存之间的交互,但是这样也依然比用GetPixel和SetPixel速度快。
讲之前先介绍一些方法的用法
Bitmap.LockBits 方法
定义如下:
Public Function LockBits (rect As Rectangle, flags As ImageLockMode, format As PixelFormat) As BitmapData
参数
rect
Rectangle
一个 Rectangle 结构,指定要锁定的 Bitmap 部分。此处可以指定要操作图的区域有利于准确处理图像
flags
ImageLockMode
一个 ImageLockMode 枚举,指定 Bitmap 的访问级别(读/写),决定该内存是读还是写的还是读写兼备的。
format
PixelFormat
一个 PixelFormat 枚举,指定此 Bitmap 的数据格式。决定每个像素的格式,每个通道应该占用多少位
返回
BitmapData
包含有关此锁定操作信息的 BitmapData。储存位图相关信息,如下:
Height
获取或设置 Bitmap 对象的像素高度。 有时也称作扫描行数。
PixelFormat
获取或设置返回此 Bitmap 对象的 BitmapData 对象中像素信息的格式。
Reserved
保留。 请勿使用。
Scan0
获取或设置位图中第一个像素数据的地址。 它也可以看成是位图中的第一个扫描行。
Stride
获取或设置 Bitmap 对象的跨距宽度(也称为扫描宽度)。
Width
获取或设置 Bitmap 对象的像素宽度。 这也可以看作是一个扫描行中的像素数。
这里讲一下跨距,跨距这个概念,其实就是一个扫描行所占的字节数,那么每个像素所占的字节就是Stride/Width
因为有的位图像素格式没有A通道所以有的像素占3个字节有的像素占4个字节甚至8字节,取决于PixelFormate的位数。比如Format32bppArgb,此格式每个像素32位,分为四个通道Argb,每个均摊8位,1byte=8bit(8位)
其他格式以此类推…
**Bitmap.UnlockBits(BitmapData) 方法
从系统内存解锁此 Bitmap。
这个方法必须要调用,否则你处理完图片后,在其他地方无法访问这图片所在的内存,也就会没法使用
Marshal.Copy 方法
托管内存与非托管内存之间的拷贝
可以理解为托管内存是.net管理(如我们自己声明的自定义类型和基本数据类型)的,非托管内存是系统管理的,比如句柄,Gdi+这些,都属于非托管对象,释放都得用相应的api释放。
这里用它的目的是将图片这个GDI对象的内存拷贝出来变成字节数组,以便我们进行操作
最重要的,Argb在内存中的位置
在内存中像素是怎么储存的呢,上面我们说到像素有Argb通道或者rgb,这里以Format32bppArgb 为例,它一个像素在内存中占用四个字节,每个通道一个字节,如下图展示3个像素

可以看到在内存中的顺序和我们说的ARGB排列顺序不同,它是BGRA刚好倒过来,没有A通道就是BGR这个顺序。在我们用Marshal的拷贝方法拷贝得到的是一个字节数组,这个数组是一维的也就是和上面图一样呈线性。数组的元素排列顺序如同图上的顺序,4字节1像素。每个元素的值均是对应上面BGRA各个通道中的值,范围0-255。
但是显然我们的图像是二维的,所以这样不利于我们理解,所以我们把一维转换为二维,如图
上面仅仅演示一个图片2*5大小的像素以及各个通道在内存中的排列,很容易我们能得到BGRA它们在内存中的公式。
这里因为选用的是32位的,所以,跨距/width=4,上面的公式可以改写成
OK,有了上面的基础知识,来快活的处理图像吧。
一维处理方法
Public Sub 处理图片(ByVal btm As Bitmap)
'将位图锁入系统内存中返回一个位图数据
Dim btmdata = btm.LockBits(New Rectangle(0, 0, btm.Width, btm.Height), ImageLockMode.ReadWrite, btm.PixelFormat)
Dim ptrscan = btmdata.Scan0 '获取第一个扫描行的内存首地址
Dim bytes(Math.Abs(btmdata.Stride) * btm.Height - 1) As Byte '声明一个托管内存数组,因为跨距可能为负所以这里用绝对值求数组长度
Marshal.Copy(ptrscan, bytes, 0, bytes.Length) '将位图数据拷贝到托管数组中
For i = 0 To bytes.Length - 1 Step 4 '这里把B通道值修改为255,因为是每个像素4字节,所以步进4
bytes(i) = 255
Next
Marshal.Copy(bytes, 0, ptrscan, bytes.Length) '处理完后把处理的数据拷贝到非托管内存中即替换原来的位图数据
btm.UnlockBits(btmdata) '解锁位图所在的内存以供我们访问
End Sub
二维处理方法(坐标处理法)
Public Sub 处理图片(ByVal btm As Bitmap)
'将位图锁入系统内存中返回一个位图数据
Dim btmdata = btm.LockBits(New Rectangle(0, 0, btm.Width, btm.Height), ImageLockMode.ReadWrite, btm.PixelFormat)
Dim ptrscan = btmdata.Scan0 '获取第一个扫描行的内存首地址
Dim bytes(Math.Abs(btmdata.Stride) * btm.Height - 1) As Byte '声明一个托管内存数组,因为跨距可能为负所以这里用绝对值求数组长度
Dim a As Integer = 0
Dim b As Integer = 0
Dim g As Integer = 0
Dim r As Integer = 0
'这里用声明一个匿名方法,只是为了使用便捷。
Dim SetPix = Sub(x As Integer, y As Integer, pixColor As Color)
b = btmdata.Stride * x / btm.Width + btmdata.Stride * y ' If(btmdata.Stride / btm.Width = 4, a + 1, a)
g = b + 1
r = g + 1
a = r + 1
If bytes(a) > 0 Then '只修改非透明色的颜色
bytes(a) = pixColor.A
bytes(b) = pixColor.B
bytes(g) = pixColor.G
bytes(r) = pixColor.R
End If
End Sub
For y = 0 To btm.Height - 1
For x = 0 To btm.Width - 1
SetPix(x, y, Color.Blue) '将所有非透明色设置为蓝色
Next
Next
Marshal.Copy(ptrscan, bytes, 0, bytes.Length) '将位图数据拷贝到托管数组中
Marshal.Copy(bytes, 0, ptrscan, bytes.Length) '处理完后把处理的数据拷贝到非托管内存中即替换原来的位图数据
btm.UnlockBits(btmdata) '解锁位图所在的内存以供我们访问
End Sub
以上便是使用marshal类处理图像的全过程,想要知道和SetPixel函数处理的速度差距,请自行测试。
虽然不能直接操作指针,但是因为能获取内存首地址可以通过内存偏移来得到其他内存地址,然后用marshal类的读写byte的方法进行直接操作内存不需要第二次的拷贝,但是差距和直接数组操作再拷贝的速度差别不是很大,具体测速可以看那位博主userbest我关注的列表里有他。
假如使用C#的指针直接操作不需要Marshal类将会更快,因为拷贝内存这个过程需要耗时,所以相对慢些。