我如何转换我的GetPixel / SetPixel颜色处理Lockbits

本文关键字:GetPixel SetPixel 颜色 Lockbits 处理 我的 何转换 转换 | 更新日期: 2023-09-27 17:51:08

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

我正试图将我的图像处理过滤器从GetPixel/SetPixel转换为Lockbits,以提高处理时间。我在堆栈溢出,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 '的未处理异常"。附加信息:位图区域已被锁定"当它到达嵌套循环的第一行时。

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

我如何转换我的GetPixel / SetPixel颜色处理Lockbits

scan0返回的数组格式为BGRA BGRA BGRA BGRA…等等......其中B = Blue, G = Green, R = Red, 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);
    }

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

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

你现在知道为什么GetPixel()很慢了,它还使用了Un/LockBits。但是对于每个单独的像素,开销会窃取cpu周期。位图只能锁定一次,这就是为什么会出现异常。这也是你不能在多个线程中同时访问位图的基本原因。

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

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

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

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