简体   繁体   English

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

[英]C# How do I convert my get GetPixel / SetPixel color processing to Lockbits?

EDIT: I deeply appreciate the replies. 编辑:我非常感谢答复。 What I need more than anything here is sample code for what I do with the few lines of code in the nested loop, since that's what works right in GetPixel/SetPixel, but also what I can't get to work right using Lockbits. 我最需要的是在嵌套循环中使用几行代码的示例代码,因为这在GetPixel / SetPixel中是正确的,但是在使用Lockbits时我也无法正常工作。 Thank you 谢谢

I'm trying to convert my image processing filters from GetPixel / SetPixel to Lockbits, to improve processing time. 我正在尝试将我的图像处理滤镜从GetPixel / SetPixel转换为Lockbits,以缩短处理时间。 I have seen Lockbits tutorials here on Stack Overflow, MSDN, and other sites as well, but I'm doing something wrong. 我在堆栈溢出,MSDN和其他站点上也看到过Lockbits教程,但是我做错了。 I'm starting with an exceedingly simple filter, which simply reduces green to create a red and purple effect. 我从一个非常简单的滤镜开始,它可以简单地减少绿色以产生红色和紫色效果。 Here's my code: 这是我的代码:

   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);
    }

So that GetPixel / SetPixel code works fine, but it's slow. 这样GetPixel / SetPixel代码可以正常工作,但是很慢。 So I tried this: 所以我尝试了这个:

    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);
    } 

Which throws the error "An unhandled exception of type 'System.InvalidOperationException' occurred in System.Drawing.dll Additional information: Bitmap region is already locked " when it reaches the first line of the nested loop. 当它到达嵌套循环的第一行时,将引发错误“在System.Drawing.dll中发生了'System.InvalidOperationException类型的未处理异常”。附加信息: 位图区域已被锁定

I realize this has to be a beginner's error, I'd appreciate if someone could demonstrate the correct way to convert this very simple filter to Lockbits. 我意识到这一定是一个初学者的错误,如果有人可以演示将这个非常简单的过滤器转换为Lockbits的正确方法,我将不胜感激。 Thank you very much 非常感谢你

The array returned by scan0 is in this format BGRA BGRA BGRA BGRA ... and so on, where B = Blue, G = Green, R = Red, A = Alpha. scan0返回的数组的格式为BGRA BGRA BGRA BGRA ...等,其中B =蓝色,G =绿色,R =红色,A = Alpha。

Example of a very small bitmap 4 pixels wide and 3 pixels height. 一个很小的位图示例,其宽度为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

To reach a certain pixel in the image (x, y) use this formula 要达到图像中的某个像素(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);
    }

You can test is yourself. 您可以测试自己。 Create a very small bitmap ( few pixels wide/height) in paint or any other program and put a breakpoint at the begining of the method. 在绘画或任何其他程序中创建一个非常小的位图(宽/高几个像素),并在该方法的开头放置一个断点。

Additional information: Bitmap region is already locked" 其他信息:位图区域已被锁定”

You now know why GetPixel() is slow, it also uses Un/LockBits under the hood. 现在您知道了为什么GetPixel()速度慢,它还在后台使用Un / LockBits。 But does so for each individual pixel, the overhead steals cpu cycles. 但是,对于每个单独的像素这样做,开销就会占用cpu周期。 A bitmap can be locked only once, that's why you got the exception. 一个位图只能被锁定一次,这就是为什么您遇到异常的原因。 Also the basic reason that you can't access a bitmap in multiple threads simultaneously. 这也是不能同时在多个线程中访问位图的基本原因。

The point of LockBits is that you can access the memory occupied by the bitmap pixels directly. LockBits的要点是,您可以直接访问位图像素占用的内存 The BitmapData.Scan0 member gives you the memory address. BitmapData.Scan0成员为您提供内存地址。 Directly addressing the memory is very fast. 直接寻址内存非常快。 You'll however have to work with an IntPtr , the type of Scan0, that requires using a pointer or Marshal.Copy(). 但是,您必须使用IntPtr (Scan0类型),它需要使用指针或Marshal.Copy()。 Using a pointer is the optimal way, there are many existing examples on how to do this, I won't repeat it here. 使用指针是最佳方法,关于如何执行操作有很多现有示例,在此不再赘述。

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

The last argument you pass is very, very important. 您传递的最后一个参数非常非常重要。 It selects the pixel format of the data and that affects the code you write. 它选择数据的像素格式,这会影响您编写的代码。 Using bmpMain.PixelFormat is the fastest way to lock but it is also very inconvenient. 使用bmpMain.PixelFormat是最快的锁定方法,但也很不方便。 Since that now requires you to adapt your code to the specific pixel format. 从那以后,您需要将代码调整为特定的像素格式。 There are many, take a good look at the PixelFormat enum . 有很多东西,请看一下PixelFormat枚举 They differ in the number of bytes taken for each pixel and how the colors are encoded in the bits. 它们在每个像素占用的字节数以及如何在位中编码颜色方面有所不同。

The only convenient pixel format is Format32bppArgb, every pixel takes 4 bytes, the color/alpha is encoded in a single byte and you can very easily and quickly address the pixels with an uint* . 唯一方便的像素格式是Format32bppArgb,每个像素占用4个字节,颜色/ alpha编码在单个字节中,您可以非常方便地使用uint*快速寻址像素。 You can still deal with Format24bppRgb but you now need a byte* , that's a lot slower. 您仍然可以处理Format24bppRgb,但是现在需要一个byte* ,这要慢得多。 The ones that have a P in the name are pre-multiplied formats, very fast to display but exceedingly awkward to deal with. 名称中带有P的是预乘格式,显示速度非常快,但处理起来却很尴尬。 You may thus be well ahead by taking the perf hit of forcing LockBits() to convert the pixel format. 因此,您可以通过强制LockBits()转换像素格式来取得成功。 Paying attention to the pixel format up front is important to avoid this kind of lossage. 预先注意像素格式对于避免这种损失很重要。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM