C#如何将获取的GetPixel / SetPixel颜色处理转换为Lockbits?

编辑:我非常感谢回复。 我在这里需要的不仅仅是示例代码,用于我在嵌套循环中使用几行代码所做的事情,因为这在GetPixel / SetPixel中是正常的,但也是我无法使用Lockbits工作的。 谢谢

我正在尝试将我的图像处理filter从GetPixel / SetPixel转换为Lockbits,以缩短处理时间。 我在Stack Overflow,MSDN和其他网站上也看过Lockbits教程,但是我做错了。 我从一个非常简单的filter开始,它简单地减少绿色以产生红色和紫色效果。 这是我的代码:

private void redsAndPurplesToolStripMenuItem_Click(object sender, EventArgs e) { // Get bitmap from picturebox Bitmap bmpMain = (Bitmap)pictureBoxMain.Image.Clone(); // search through each pixel via x, y coordinates, examine and make changes. Dont let values exceed 255 or fall under 0. for (int y = 0; y < bmpMain.Height; y++) for (int x = 0; x < bmpMain.Width; x++) { bmpMain.GetPixel(x, y); Color c = bmpMain.GetPixel(x, y); int myRed = cR, myGreen = cG, myBlue = cB; myGreen -= 128; if (myGreen < 0) myGreen = 0; bmpMain.SetPixel(x, y, Color.FromArgb(255, myRed, myGreen, myBlue)); } // assign the new bitmap to the picturebox pictureBoxMain.Image = (Bitmap)bmpMain; // Save a copy to the HD for undo / redo. string myString = Environment.GetEnvironmentVariable("temp", EnvironmentVariableTarget.Machine); pictureBoxMain.Image.Save(myString + "\\ColorAppRedo.png", System.Drawing.Imaging.ImageFormat.Png); } 

因此GetPixel / SetPixel代码工作正常,但速度很慢。 所以我尝试了这个:

  private void redsAndPurplesToolStripMenuItem_Click(object sender, EventArgs e) { // Get bitmap from picturebox Bitmap bmpMain = (Bitmap)pictureBoxMain.Image.Clone(); Rectangle rect = new Rectangle(Point.Empty, bmpMain.Size); BitmapData bmpData = bmpMain.LockBits(rect, ImageLockMode.ReadOnly, bmpMain.PixelFormat); // search through each pixel via x, y coordinates, examine and make changes. Dont let values exceed 255 or fall under 0. for (int y = 0; y < bmpMain.Height; y++) for (int x = 0; x < bmpMain.Width; x++) { bmpMain.GetPixel(x, y); Color c = new Color(); int myRed = cR, myGreen = cG, myBlue = cB; myGreen -= 128; if (myGreen < 0) myGreen = 0; bmpMain.SetPixel(x, y, Color.FromArgb(255, myRed, myGreen, myBlue)); } bmpMain.UnlockBits(bmpData); // assign the new bitmap to the picturebox pictureBoxMain.Image = (Bitmap)bmpMain; // Save a copy to the HD for undo / redo. string myString = Environment.GetEnvironmentVariable("temp", EnvironmentVariableTarget.Machine); pictureBoxMain.Image.Save(myString + "\\ColorAppRedo.png", System.Drawing.Imaging.ImageFormat.Png); } 

这会引发错误“System.Drawing.dll中发生类型’System.InvalidOperationException’的未处理exception附加信息:当位图区域到达嵌套循环的第一行时,它已被锁定 ”。

我意识到这必须是一个初学者的错误,如果有人能够演示将这个非常简单的filter转换为Lockbits的正确方法,我会很感激。 非常感谢你

附加信息:位图区域已被锁定“

你现在知道为什么GetPixel()很慢,它也使用Un / LockBits。 但是对于每个单独的像素都是这样,开销窃取cpu周期。 位图只能锁定一次,这就是你得到exception的原因。 此外,您无法同时访问多个线程中的位图的基本原因。

LockBits的要点是你可以直接访问位图像素占用的内存 。 BitmapData.Scan0成员为您提供内存地址。 直接寻址内存非常快。 但是,您必须使用需要使用指针或Marshal.Copy()的IntPtr ,即Scan0的类型。 使用指针是最佳方式,有许多现有的例子如何做到这一点,我在此不再重复。

  ... = bmpMain.LockBits(rect, ImageLockMode.ReadOnly, bmpMain.PixelFormat); 

你传递的最后一个论点非常非常重要。 它选择数据的像素格式并影响您编写的代码。 使用bmpMain.PixelFormat是最快的锁定方式,但它也非常不方便。 因为现在需要您使代码适应特定的像素格式。 有很多,看看PixelFormat枚举 。 它们的不同之处在于每个像素的字节数以及颜色在位中的编码方式。

唯一方便的像素格式是Format32bppArgb,每个像素占用4个字节,颜色/ alpha编码为单个字节,您可以使用uint*非常轻松快速地处理像素。 您仍然可以处理Format24bppRgb,但现在需要一个byte* ,这要慢得多。 名称中有P的是预乘的格式,显示速度非常快,但处理起来却非常尴尬。 因此,你可以通过强制使用LockBits()转换像素格式来实现性能提升。 预先注意像素格式对于避免这种损失很重要。

scan0返回的数组格式为BGRA BGRA BGRA BGRA …依此类推,其中B =蓝色,G =绿色,R =红色,A = Alpha。

一个非常小的位图示例,宽4像素,高3像素。

 BGRA BGRA BGRA BGRA BGRA BGRA BGRA BGRA BGRA BGRA BGRA BGRA stride = width*bytesPerPixel = 4*4 = 16 bytes height = 3 maxLenght = stride*height= 16*3 = 48 bytes 

要到达图像中的某个像素(x,y),请使用此公式

 int certainPixel = bytesPerPixel*x + stride * y; B = scan0[certainPixel + 0]; G = scan0[certainPixel + 1]; R = scan0[certainPixel + 2]; A = scan0[certainPixel + 3]; 

  public unsafe void Test(Bitmap bmp) { int width = bmp.Width; int height = bmp.Height; //TODO determine bytes per pixel int bytesPerPixel = 4; // we assume that image is Format32bppArgb int maxPointerLenght = width * height * bytesPerPixel; int stride = width * bytesPerPixel; byte R, G, B, A; BitmapData bData = bmp.LockBits( new System.Drawing.Rectangle(0, 0, bmp.Width, bmp.Height), ImageLockMode.ReadWrite, bmp.PixelFormat); byte* scan0 = (byte*)bData.Scan0.ToPointer(); for (int i = 0; i < maxPointerLenght; i += 4) { B = scan0[i + 0]; G = scan0[i + 1]; R = scan0[i + 2]; A = scan0[i + 3]; // do anything with the colors // Set the green component to 0 G = 0; // do something with red R = R < 54 ? (byte)(R + 127) : R; R = R > 255 ? 255 : R; } bmp.UnlockBits(bData); } 

你可以自己测试一下。 在绘画或任何其他程序中创建一个非常小的位图(几个像素宽/高),并在方法的开头放置一个断点。