编辑:我非常感谢回复。我在这里需要的不仅仅是示例代码,用于我在嵌套循环中使用几行代码所做的事情,因为这在GetPixel / SetPixel中可以正常工作,而且我使用Lockbits无法正常工作。谢谢
我正在尝试将我的图像处理过滤器从GetPixel / SetPixel转换为Lockbits,以缩短处理时间。 我已经在Stack Overflow,MSDN和其他网站上看到了Lockbits教程,但我做错了。我开始使用一个非常简单的过滤器,它只是简化了绿色来创建一个红色和紫色的效果。这是我的代码:
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 = c.R, myGreen = c.G, myBlue = c.B;
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 = c.R, myGreen = c.G, myBlue = c.B;
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'的未处理异常附加信息:位图区域已锁定”当它到达嵌套的第一行时环。
我意识到这必须是一个初学者的错误,如果有人能够演示正确的方法将这个非常简单的过滤器转换为Lockbits,我会很感激。非常感谢你
答案 0 :(得分:4)
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);
}
你可以自己测试。在绘画或任何其他程序中创建一个非常小的位图(几个像素宽/高),并在方法的开头放置一个断点。
答案 1 :(得分:4)
附加信息:位图区域已被锁定“
你现在知道为什么GetPixel()很慢,它还使用了Un / LockBits。但是对于每个单独的像素都是这样,开销窃取cpu周期。位图只能被锁定一次,这就是你得到异常的原因。也是您无法同时访问多个线程中的位图的基本原因。
LockBits的意思是你可以直接访问位图像素占用的内存。 BitmapData.Scan0成员为您提供内存地址。直接寻址内存非常快。但是,您必须使用需要使用指针或Marshal.Copy()的IntPtr
,即Scan0的类型。使用指针是最佳方式,有很多关于如何执行此操作的示例,我在此不再重复。
... = bmpMain.LockBits(rect, ImageLockMode.ReadOnly, bmpMain.PixelFormat);
你传递的最后一个参数是非常非常重要的。它选择数据的像素格式并影响您编写的代码。使用bmpMain.PixelFormat是最快的锁定方式,但它也非常不方便。因为现在需要您使代码适应特定的像素格式。有很多,好好看看PixelFormat enum。它们的不同之处在于每个像素的字节数以及颜色在位中的编码方式。
唯一方便的像素格式是Format32bppArgb,每个像素占用4个字节,颜色/ alpha编码为单个字节,您可以使用{{1}非常轻松快速地处理像素}。您仍然可以处理Format24bppRgb,但现在需要uint*
,这会慢得多。名称中包含P的那些是预乘格式,显示速度非常快但处理起来非常尴尬。因此,你可以通过强制使用LockBits()转换像素格式来实现性能提升。预先注意像素格式对于避免这种损失非常重要。