大部分公司进行图像处理时所采用的开发工具之首选肯定是VC,这里主要是考虑效率问题。VC中通过指针直接可以访问内存,而VB为了安全起见把这些操作完全封装在底层。这种以牺牲程序效能来换取开发效率的做法对于图像来说不合适,因为图像的计算量通常都是很大的。因此,要想利用VB高效的图像处理,除了要能快速获取图像数据外,能否直接操作内存数据成为了关键。
在C和C++里一个数组指针和数组第一个元素的指针是一回事,他们在内存中是一个接着一个线性存放的,我们通过第一个元素就能访问随后的元素,这样的数组我们称之为“真数组”,但是他不安全。因为我们无法从这种真数组的指针上得知数组的维数、元素个数等非常重要的信息,所以也无法控制对这种数组的访问。而在VB中,所采用的数组在COM里叫做SafeArray,他能够解决上述“真数组”的安全问题。那么SafeArray结构如下:
- Private Type SAFEARRAY
- cDims As Integer '这个数组有几维?
- fFeatures As Integer '这个数组有什么特性?
- cbElements As Long '数组的每个元素有多大?
- cLocks As Long '这个数组被锁定过几次?
- pvData As Long '这个数组里的数据放在什么地方?
- 'rgsabound() As SFArrayBOUND
- End Type
- Private Type SAFEARRAYBOUND
- cElements As Long '这一维有多少个元素?
- lLbound As Long '它的索引从几开始?
- End Type
这里我们把焦点放在SAFEARRAY结构的pvData成员上,正是这个成员只出了我们定义的数组的真数据第一个元素放在内存的那个位置。这是一个很有用的数据。
在VC中操作图像数据,一般都是直接移动指针到需要的地方,然后取值,我们也希望能够在VB中有类似的操作。
当我们把图像加载到内存后,我们是可以获得图像数据在内存中的首地址的,如果我们能够把VB中某数组对应的SafeArray之pvData指向到这个首地址,那么我们操作数组也就相当于操作了对应的内存数据。这种方法的实现很简单,具体的代码可以参考:
http://blog.csdn.net/zyl910/archive/2006/05/24/752111.aspx
但是,上述这种结构在操作上似乎还是很VB化,和VC中的图像处理的过程不太类似。
但是如果使用如下的函数,你会发现整个操作的过程意义很明朗。
- Public Sub MakePoint(ByVal DataArrPtr As Long,ByVal pDataArrPtr As Long,ByRef OldArrPtr As Long,ByRef OldpArrPtr As Long)
- Dim Temp As Long,TempPtr As Long
- CopyMemory Temp,ByVal DataArrPtr,4 '得到DataArrPtr的SAFEARRAY结构的地址
- Temp = Temp + 12 '这个指针偏移12个字节后就是pvData指针
- CopyMemory TempPtr,ByVal pDataArrPtr,4 '得到pDataArrPtr的SAFEARRAY结构的地址
- TempPtr = TempPtr + 12 '这个指针偏移12个字节后就是pvData指针
- CopyMemory OldpArrPtr,ByVal TempPtr,4 '保存旧地址
- CopyMemory ByVal TempPtr,Temp,4 '使pDataArrPtr指向DataArrPtr的SAFEARRAY结构的pvData指针
- CopyMemory OldArrPtr,ByVal Temp,4 '保存旧地址
- End Sub
- MakePoint VarPtrArray(DataArr),VarPtrArray(pDataArr),OldArrPtr,OldpArrPtr
- pDataArr(0)=m_Pointer
其中各变量的定义如下:
- Dim DataArr(0 To 3) As Byte,pDataArr(0 To 0) As Long
- Dim OldArrPtr As Long,OldpArrPtr As Long
那么我们稍微解释下上面的函数。
乍一看,这个代码完全和SAFEARRAY结构没有关系啊。实际上,这个过程到处充斥着SAFEARRAY结构。
首先,我们看下VarPtrArray这个API函数,他的声明如下:
- Declare Function VarPtrArray Lib "msvbvm60.dll" Alias "VarPtr" (Var() As Any) As Long
从别名Alias "VarPtr"上看,他就是VB中隐藏的与针相关的函数VarPtr,只过参数声明上用的是VB数组,这时它返回来的就是一个指向数组SafeArray结构的指针的指针。因为VarPtr会将传给它的参数的地址返回,而用ByRef传给它一个VB数组,实际上传递的是一个SafeArray结构的指针,这时VarPtrArray将返回这个指针的指针。
简单的说,调用了VarPtrArray(DataArr),我们就知道了DataArr这个数组的所对应的SAFEARRAY结构在内存的地址。
MakePoint 中的第一句:CopyMemory Temp,4的意思是把DataArr这个数组的所对应的SAFEARRAY结构在内存的地址临时性的保存到Temp变量中。
TempPtr = TempPtr + 12 ,这里的12怎么来的呢,我们计算下SAFEARRAY结构中pvData相对于结构的偏移地址,Integer+ Integer+ Long+ Long=12,你大概也知道什么意思了吧。
下面两行的代码意义雷同。
- CopyMemory OldpArrPtr,4这行代码的意思是把pDataArr的真数组的内存起始地址保存起来,以便以后恢复,注意这里用的ByVal。
CopyMemory ByVal TempPtr,4意思是把DataArr的真数组的内存起始值赋值给pDataArr的真数组的内存起始值。
CopyMemory OldArrPtr,4这行代码的意思是把DataArr的真数组的内存起始地址保存起来,以便以后恢复,注意这里用的ByVal。
经过了以上操作,如果我们改变了pDataArr(0)的值,相当于是改变了DataArr这个数组的首元素在内存中的地址,因此和VC中的操作基本类似了。
假设我们如果已经创建了一副24位的内存图像,则代码如下
- Dim i As Long,j As Long
- Dim DataArr(0 To 3) As Byte,OldpArrPtr As Long
- Dim LineAddBytes As Long
- LineAddBytes = m_Stride - m_Width*3
- MakePoint VarPtrArray(DataArr),OldpArrPtr
- pDataArr(0)=m_Pointer ‘m_Pointer为图像在内存中的首地址
- For j = 0 To m_Hegiht - 1
- For i = 0 To m_Width - 1
- DataArr(2) = 255 - DataArr(2) ‘简单的反色算法
- DataArr(1) = 255 - DataArr(1)
- DataArr(0) = 255 - DataArr(0)
- pDataArr(0) = pDataArr(0) + 3
- Next
- pDataArr(0) = pDataArr(0) + LineAddBytes
- Next
- FreePoint VarPtrArray(DataArr),OldpArrPtr
代码行LineAddBytes = m_Stride - m_Width*3的存在是因为24位图像的一个扫描行宽度在很多情况下并不是图像的宽度的3倍,我们需要跳过无用的数据。
pDataArr(0)=m_Pointer则把我们的DataArr的第一个元素DataArr(0)赋予了访问m_Pointe处字节的能力,一般这里是图像的Green分量。对应的DataArr(1)则为Blue分量。
pDataArr(0) = pDataArr(0) + 3这里的意思是我们要把指针偏移3个字节,以便访问下一个像素。
pDataArr(0) = pDataArr(0) + LineAddBytes是为了扫描行对次的。
如果以上代码你看不懂,那简单的说,如果你要访问图像第m行,n列的元素,则可以如下调用:
pDataArr(0) = m_Pointer+m_Stride*(m-1)+(n-1)*3 (24位乘3,32位乘4,8位乘1,8位一下要做特殊处理),这相当于VC中的移动指针。
然后就可以访问DataArr的各元素值得到对应的R/G/B了。
上述FreePoint的过程如下,主要是用来恢复VB的数组的。应该保证每次调用MakePoint函数都有对应调用FreePoint函数。否则有可能会出现IDE崩溃。
- Public Sub FreePoint(ByVal DataArrPtr As Long,ByVal OldArrPtr As Long,ByVal OldpArrPtr As Long)
- Dim TempPtr As Long
- CopyMemory TempPtr,4 '得到DataArrPtr的SAFEARRAY结构的地址
- CopyMemory ByVal (TempPtr + 12),4 '恢复旧地址
- CopyMemory TempPtr,4 '得到pDataArrPtr的SAFEARRAY结构的地址
- CopyMemory ByVal (TempPtr + 12),OldpArrPtr,4 '恢复旧地址
- End Sub
从执行效率上来讲,这种指针功能比VC中的真指针是要稍微慢一些,但是绝对不是人们观念的中那么大的差距,应该能在90%以上。
要注意的是,如果一不小心,指针所指向的地址超出了图像在内存中数据分布,则会弹出一个错误,该错误会导致VB崩溃,因此,在调试代码前一定要记得保存代码。