翻译自:Using the LockBits method to access image data

很多图像处理任务,甚至图像文件格式的转换,例如将32位影像转为8位影像,直接访问像素数据数组比使用GetPixelSetPixel方法的速度要快很多。

大家都知道.NET是托管代码系统,它最常使用的是托管数据,所以我们并不经常需要访问存贮在内存中的字节。但是,图像的处理是少有的,需要访问内存中的字节。引文访问托管数据的速度太慢了。所以我们必须深入探究操作图像数据的方法。

在进入主题之前,需要注意的是:不同的编程语言访问非托管数据的方法不同。c#程序员可以借助unsafe关键字使用指针访问内存中的数据。VB程序员需要借助Marshal类中的方法,这在性能上有一些小小的缺陷。

Lock up your bits

Bitmap类提供了LockBits方法以及对应的UnlockBits方法。你可以锁定内存中的部分图像像素数据数组,然后直接访问并可修改bitmap中的二进制位。LockBits返回的BitmapDa ta类型的数据描述了锁定的数组中数据的布局和位置信息。

BitmapData类有以下重要属性:

  • Scan0 锁定数据数组在内存中的地址。
  • Stride 一个单行像素数据的宽度,以byte为单位。这一宽度是图像像素宽度的整数倍或非整数倍,然后很有可能地再填充另外的一些字节。细节等下详述。
  • PixelFormat 数据实际的像素格式,这对于找到正确的字节非常重要。
  • Width 锁定影像的宽度
  • Height 锁定影像的高度

内存中数组的Scan0和Stride的关系如下图:

The basic layout of a locked bitmap array

图1: The basic layout of a locked bitmap array

属性Stride,如图1中所示的那样,持有行的宽度,以字节为单位。考虑到效率,行的宽度并不一定是确切的像素大小的倍数(整数倍 Or 非整数倍)。系统需要保证数据被装入行,以4字节的边界开始,并填补一些字节以形成4个字节倍数的布局。比如一幅24位的影像宽度为17像素,它的Stride的宽为52。每行数据将占3*17=51字节,再加上填补的一个字节,每行扩展到52字节,即13*4字节;一幅4位的索引影像,宽度为17像素,它的Stride的值为12,9个字节,更为准确的说是8.5个字节为数据区,然后还要再填补另外的3个字节以形成4字节的边界。

一行中含有数据的部分的排列依据于PixelFormat。一幅24位的影像持有RGB数据,每3个字节1个像素;24位的影像持有RGBA数据,每4个字节1个像素。每1字节含有多于1个像素的像素格式(pixel format),比如4位索引影像、1位索引影像,这类影像需要认真处理,不要将同一字节中相邻的像素混淆了。

Finding the right byte

因为Stride是行宽,想要索引到任意行的任意位置,我们可以通过Stride*Y得到一特定行的起始位置。在一行中获取正确的像素或许有些复杂,这要求了解像素格式的布局。下面的例子讲述了如何访问到给定像素格式的特定像素。

  • Format32BppArgb 已知X,Y坐标,像素第一个元素的位置为Scan0+(y*Stride)+(x*4)。这是blue字节的位置。接下来的3个字节分别含有green、red、alpha数据。
  • Format24BppRgb 已知X,Y坐标,像素第一个元素的位置为Scan0+(Y*Stride)+(X*3)。这是blue字节的位置,接下来的2个字节分别含有green、red数据。
  • Format8BppIndexed 已知X,Y坐标,字节的地址为Scan0+(Y*Stride)+X,字节中的数据指向256调色板。
  • Format4BppIndexed 已知X,Y坐标,字节的地址为Scan0+(Y*Stride)+(X/2),每个字节包含2个像素,前半个字节在第1个像素,后半个字节在第2个像素。这两个半字节的4个bit位的值在16位的调色板上选取。
  • Format1BppIndexed 已知X,Y坐标,字节的地址为Scan0+(Y*Stride)+(X/2),每个字节有8个bit位,每个bit位代表1个像素,最左边的像素为第8个bit位,最右边的像素为第1个bit位。bit位的值在只有两个值的调色板上选取。

Iterating through the pixels(像素遍历)

每个像素1到多个字节的像素格式,遍历公式很简单,通过X,Y坐标循环就能实现。下面的代码将32位的影像中的Blue值设为255。其中,bm为Bitmap类型。

BitmapData bmd=bmLockBits(new Rectangle(0,0,10,10),    System.Drawing.Imaging.ImageLockMode.ReadOnly,bm.PixelFormat);
int PixelSize=4;
for(int y=0;y<bmd.Height;y++)     {     }
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new frmMain());
//Application.Run(new frmBands());
}