C#设置像素颜色的更快方法 [英] c# faster way of setting pixel colours

查看:185
本文介绍了C#设置像素颜色的更快方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我选择了一个使用setPixel()创建噪声贴图的项目.此应用程序的大部分运行时都用在setPixel()函数中,因此我希望提高该函数的执行速度.

我对此进行了一些研究,结果发现:

int index = x + (y * Width);
int col = color.ToArgb();

if (this.Bits == null)
{
    this.Bits = new Int32[Width * Height];
}

Bits[index] = col;

建议使用

作为更快的方法.但是,这会生成一个全黑的图像.

我不了解100%的图像操作和内存指针如何能够完全理解代码并将其重构为更好的东西.

这是该人实施之前的原始代码:

            unsafe
            {
                var scan0 = (byte*)Iptr;
                int bitmapStride = Stride;
                int bitmapPixelFormatSize = Depth / 8;

                index = (bitmapStride * y) + (x * bitmapPixelFormatSize);
                if (bitmapPixelFormatSize == 4)
                {
                    scan0[index + 3] = color.A;
                    scan0[index + 2] = color.R;
                    scan0[index + 1] = color.G;
                    scan0[index] = color.B;

                }
                else if (bitmapPixelFormatSize == 1)
                {
                    scan0[index] = color.R;

                }
                else
                {
                    scan0[index + 2] = color.R;
                    scan0[index + 1] = color.G;
                    scan0[index] = color.B;

                }
            }

Iptr仅仅是一个IntPtr

Stride是一个整数,我可以找到的唯一位置是Stride = (PixelCount/Height) * (Depth / 8)

x是宽度

y是高度

我能够对原始代码块中发生的事情进行解释,并且可能对理解如何将其转换为执行速度更快的代码有帮助,目前由于完成此功能大约需要500,000毫秒才能完成此功能.嵌套用于宽度*高度的循环.

解决方案

注意:以下信息最初是由鲍勃·鲍威尔(Bob Powell)创建的.原始链接(不再起作用)为 http://bobpowell.net/lockingbits.htm .

我已从 https://web.archive.org/web/20120330012542/http://bobpowell.net/lockingbits.htm .它有点长,但我认为值得保留.

我不确定这是否可以直接回答您的问题,但是也许可以帮助您找到解决方案.


使用LockBits方法访问图像数据

许多图像处理任务,甚至文件类型转换,例如从每像素32位到每像素8位的转换,都可以通过直接访问像素数据数组来加速,而不是依赖于GetPixel和SetPixel或其他方法

您将意识到,.NET是最常使用托管数据的托管代码系统,因此我们不再经常需要访问存储在内存中的字节,但是,图像处理是托管数据中为数不多的几种操作之一访问太慢了,因此我们需要再次深入研究查找数据和操作数据的棘手问题.

在我开始讨论本主题之前,我只是提醒您,根据程序的编写语言,用于访问任何非托管数据的方法将有所不同. C#开发人员有机会通过unsafe关键字和使用指针直接访问内存中的数据. Visual Basic程序员应通过Marshal类方法访问此类数据,这也可能会导致较小的性能损失.

锁定你的位

Bitmap类提供了LockBits和相应的UnlockBits方法,使您可以修复内存中位图像素数据数组的一部分,直接对其进行访问,最后用修改后的数据替换位图中的位. LockBits返回一个BitmapData类,该类描述了锁定数组中数据的布局和位置.

BitmapData类包含以下重要属性;

  • Scan0 固定数据阵列内存中的地址
  • 跨度(像素)单行像素数据的宽度(以字节为单位).这个宽度 是像素尺寸的倍数,或者可能是约倍数 图片,并可能被填充以包含更多字节.患病的 尽快解释原因.
  • PixelFormat .数据的实际像素格式.这个很重要 查找正确的字节
  • 宽度锁定图像的宽度
  • 高度锁定图像的高度

Scan0和Stride与内存中的数组的关系如图1所示.

Stride属性(如图1所示)保存一行的宽度(以字节为单位).但是,行的大小可能不是像素大小的精确倍数,因为为了提高效率,系统确保将数据打包到以四个字节为边界的行中,并填充为四个字节的倍数.例如,这意味着一个17位宽的每像素24位图像的步幅为52.每行中使用的数据将占用3 * 17 = 51字节,而1字节的填充将每行扩展为52字节或13 * 4字节.一张17像素宽的4BppIndexed图像的步幅为12.其中九个字节(或更合适的是八个半字节)将包含数据,并且该行将被另外3个字节填充到4字节边界. >

如上所述,该行的数据承载部分根据像素格式进行布局.包含RGB数据的每像素24位图像将每3个字节有一个新像素,每像素RGBA每4个字节有32位.每字节包含一个以上像素的像素格式(例如,每个像素索引4位和每个像素索引1位)必须仔细处理,以使所需的像素不会与同一字节中的近邻像素混淆. >

找到正确的字节.

因为跨度是行的宽度,所以要索引任何给定的行或Y坐标,可以将跨度乘以Y坐标以获得特定行的开始.在行中查找正确的像素可能更加困难,并且取决于了解像素格式的布局.以下示例显示了如何以给定像素格式访问特定像素.

  • Format32BppArgb 给定X和Y坐标,第一个的地址 像素中的元素为Scan0 +(y * stride)+(x * 4).这指向 蓝色字节.以下三个字节包含绿色,红色和Alpha 字节.
  • Format24BppRgb 给定X和Y坐标,第一个的地址 像素中的元素为Scan0 +(y * Stride)+(x * 3).这指向 蓝色字节,然后是绿色和红色.
  • Format8BppIndexed 给定X和Y坐标, 字节是Scan0 +(y * Stride)+ x.该字节是图像的索引 调色板.
  • Format4BppIndexed 给定X和Y坐标,该字节包含 像素数据计算为Scan0 +(y * Stride)+(x/2).相应的 字节包含两个像素,高半字节是最左边, 下半字节是两个像素的最右边.的四位 上半部分和下半部分用于从16种颜色中选择颜色 调色板.
  • Format1BppIndexed 给定X和Y坐标,该字节包含 通过Scan0 +(y * Stride)+(x/8)计算像素.该字节包含 8位,每个位是一个像素,第8位中最左边的像素 位0中最右边的像素.这些位从两个条目中选择 调色板.

遍历像素

对于每个像素一个或多个字节的像素格式,公式很简单,可以通过按顺序遍历所有Y和X值来实现.以下清单中的代码将每像素32位图像的蓝色分量设置为255.在两种情况下,bm都是先前创建的位图.

BitmapData bmd=bm.LockBits(new Rectangle(0, 0, 10, 10), System.Drawing.Imaging.ImageLockMode.ReadOnly, bm.PixelFormat);

int PixelSize=4;

for(int y=0; y<bmd.Height; y++)
{
    byte* row = (byte *)bmd.Scan0+(y*bmd.Stride);
    for(int x = 0; x<bmd.Width; x++)
    {
        row[x * PixelSize] = 255;
    }
}

在VB中,此操作将有所不同,因为VB不了解指针,并且需要使用marshal类来访问非托管数据.

Dim x As Integer
Dim y As Integer
Dim PixelSize As Integer = 4

Dim bmd As BitmapData = bm.LockBits(new Rectangle(0, 0, 10, 10), System.Drawing.Imaging.ImageLockMode.ReadOnly, bm.PixelFormat)

For y = 0 To bmd.Height - 1
    For x = 0 To bmd.Width - 1
        Marshal.WriteByte(bmd.Scan0, (bmd.Stride * y) + (4 * x) , 255)
    Next
Next

子字节像素.

前面提到的Format4BppIndexed和Format1BppIndexed像素格式在一个字节中都存储有多个像素.在这种情况下,您需要确保更改一个像素的数据不会影响该字节中保存的其他一个或多个像素.

为每个像素图像索引1位的方法依赖于使用按位逻辑运算AndOr重置或设置字节中的特定位.对每像素图像1位使用上面所示的公式后,X坐标的低3位用于选择所需的位.下面的清单在C#和VB中显示了此过程.在这两个示例中,bmd是从每像素图像1位中提取的位图数据.

C#代码使用指针,并要求使用不安全的代码进行编译

byte* p=(byte*)bmd.Scan0.ToPointer();

int index=y*bmd.Stride+(x>>3);

byte mask=(byte)(0x80>>(x&0x7));

if(pixel)
    p[index]|=mask;
else
    p[index]&=(byte)(mask^0xff);

VB代码使用元帅类

Dim mask As Byte = 128 >> (x And 7)

Dim offset As Integer = (y * bmd.Stride) + (x >> 3)

Dim currentPixel As Byte = Marshal.ReadByte(bmd.Scan0, offset)

If pixel = True Then
    Marshal.WriteByte(bmd.Scan0, offset, currentPixel Or mask)
Else
    Marshal.WriteByte(bmd.Scan0, offset, CByte(currentPixel And (mask Xor 255)))
End If

注意,使用C#代码中的Marshal类是非常有效的.我使用了指针,因为它可以提供最佳性能.

以类似方式处理访问每个像素4位图像中的单个像素.字节的上下半字节必须分别处理,并且更改奇数X像素的内容不应影响偶数X像素.下面的代码显示了如何在C#和VB中执行此操作.

C#

int offset = (y * bmd.Stride) + (x >> 1);

byte currentByte = ((byte *)bmd.Scan0)[offset];

if((x&1) == 1)
{
    currentByte &= 0xF0;
    currentByte |= (byte)(colorIndex & 0x0F);
}
else
{
    currentByte &= 0x0F;
    currentByte |= (byte)(colorIndex << 4);
}

((byte *)bmd.Scan0)[offset]=currentByte;

VB

Dim offset As Integer = (y * bmd.Stride) + (x >> 1)

Dim currentByte As Byte = Marshal.ReadByte(bmd.Scan0, offset)

If (x And 1) = 1 Then
    currentByte = currentByte And &HF0
    currentByte = currentByte Or (colorIndex And &HF)
Else
    currentByte = currentByte And &HF
    currentByte = currentByte Or (colorIndex << 4)
End If

Marshal.WriteByte(bmd.Scan0, offset, currentByte)

使用LockBits和UnlockBits

LockBits方法采用一个矩形,该矩形的大小可以等于或小于正在处理的图像,PixelFormat通常与正在处理的图像相同,并且ImageLockMode值指定是否读取数据-only,只写,读写或用户分配的缓冲区.在C#或VB中不能使用最后一个选项,因为指定用户缓冲区的LockBits的方法重载未包含在GDI +托管包装器中.

完成所有操作后,非常重要的一点是,使用UnlockBits方法将BitmapData放回位图中.下面的代码段对此进行了说明.

Dim bmd As BitmapData = bm.LockBits(New Rectangle(0, 0, 10, 10), ImageLockMode.ReadWrite, bm.PixelFormat)

    ' do operations here

bm.UnlockBits(bmd)

摘要

这几乎涵盖了直接访问最流行和最困难的像素格式的各个方面.使用这些技术代替Bitmap提供的GetPixelSetPixel方法将显着提高图像处理和图像格式转换例程的性能.

I've picked up a project that creates a noise map using setPixel(). The majority of the runtime of this application is spent within the setPixel() function so I was looking to increase the speed at which the function executes.

I have done some research into this and found that this:

int index = x + (y * Width);
int col = color.ToArgb();

if (this.Bits == null)
{
    this.Bits = new Int32[Width * Height];
}

Bits[index] = col;

has been recommended as a quicker approach. However, this is generating a completely black image.

I don't understand 100% how image manipulation and memory pointers work to be able to understand the code completely and refactor it to something better.

Here's the original code the person before implemented:

            unsafe
            {
                var scan0 = (byte*)Iptr;
                int bitmapStride = Stride;
                int bitmapPixelFormatSize = Depth / 8;

                index = (bitmapStride * y) + (x * bitmapPixelFormatSize);
                if (bitmapPixelFormatSize == 4)
                {
                    scan0[index + 3] = color.A;
                    scan0[index + 2] = color.R;
                    scan0[index + 1] = color.G;
                    scan0[index] = color.B;

                }
                else if (bitmapPixelFormatSize == 1)
                {
                    scan0[index] = color.R;

                }
                else
                {
                    scan0[index + 2] = color.R;
                    scan0[index + 1] = color.G;
                    scan0[index] = color.B;

                }
            }

Iptr is just an IntPtr

Stride is an int, the only place I can find this being set is Stride = (PixelCount/Height) * (Depth / 8)

x is width

y is height

Would I be able to get an explanation of what is happening in the original block of code and possibly some help in understanding how to convert that to something that executes quicker, currently it takes around 500,000ms to finish this function due to a nested for loop of width * height.

解决方案

Note: The following information was originally created by Bob Powell. The original link (no longer functional) is http://bobpowell.net/lockingbits.htm.

I have copied this information from the Internet Archive at https://web.archive.org/web/20120330012542/http://bobpowell.net/lockingbits.htm. It's a bit long, but I think it's worth preserving.

I'm not sure if this will serve as a direct answer to your question, but perhaps it will help you in finding a solution.


Using the LockBits method to access image data

Many image processing tasks and even file type conversions, say from 32 bit-per-pixel to 8 bit-per-pixel can be speeded up by accessing the pixel data array directly, rather than relying on GetPixel and SetPixel or other methods.

You will be aware that .NET is a managed code system which most often uses managed data so it's not often that we need to gain access to bytes stored in memory anymore however, image manipulation is one of the few times when managed data access is just too slow and so we need to delve once again into the knotty problems of finding the data and manipulating it.

Before I start on the subject in hand, I'll just remind you that the methods used to access any unmanaged data will be different depending on the language in which your program is written. C# developers have the opportunity, via the unsafe keyword and use of pointers, to access data in memory directly. Visual basic programmers should access such data through the Marshal class methods which may also show a small performance loss.

Lock up your bits

The Bitmap class provides the LockBits and corresponding UnlockBits methods which enable you to fix a portion of the bitmap pixel data array in memory, access it directly and finally replace the bits in the bitmap with the modified data. LockBits returns a BitmapData class that describes the layout and position of the data in the locked array.

The BitmapData class contains the following important properties;

  • Scan0 The address in memory of the fixed data array
  • Stride The width, in bytes, of a single row of pixel data. This width is a multiple, or possibly sub-multiple, of the pixel dimensions of the image and may be padded out to include a few more bytes. I'll explain why shortly.
  • PixelFormat The actual pixel format of the data. This is important for finding the right bytes
  • Width The width of the locked image
  • Height The height of the locked image

The relationship of Scan0 and Stride to the array in memory is shown in figure1.

The Stride property, as shown in figure 1, holds the width of one row in bytes. The size of a row however may not be an exact multiple of the pixel size because for efficiency, the system ensures that the data is packed into rows that begin on a four byte boundary and are padded out to a multiple of four bytes. This means for example that a 24 bit per pixel image 17 pixels wide would have a stride of 52. The used data in each row would take up 3*17 = 51 bytes and the padding of 1 byte would expand each row to 52 bytes or 13*4 bytes. A 4BppIndexed image of 17 pixels wide would have a stride of 12. Nine of the bytes, or more properly eight and a half, would contain data and the row would be padded out with a further 3 bytes to a 4 byte boundary.

The data carrying portion of the row, as has been suggested above, is laid out according to the pixel format. A 24 bit per pixel image containing RGB data would have a new pixel every 3 bytes, a 32 bit per pixel RGBA every four bytes. Pixel formats that contain more than one pixel per byte, such as the 4 bit per pixel Indexed and 1 bit per pixel indexed, have to be processed carefully so that the pixel required is not confused with it's neigbour pixels in the same byte.

Finding the right byte.

Because the stride is the width of a row, to index any given row or Y coordinate you can multiply the stride by the Y coordinate to get the beginning of a particular row. Finding the correct pixel within the row is possibly more difficult and depends on knowing the layout of the pixel formats. The following examples show how to access a particular pixel for a given pixel format.

  • Format32BppArgb Given X and Y coordinates, the address of the first element in the pixel is Scan0+(y * stride)+(x*4). This Points to the blue byte. The following three bytes contain the green, red and alpha bytes.
  • Format24BppRgb Given X and Y coordinates, the address of the first element in the pixel is Scan0+(y*Stride)+(x*3). This points to the blue byte which is followed by the green and the red.
  • Format8BppIndexed Given the X and Y coordinates the address of the byte is Scan0+(y*Stride)+x. This byte is the index into the image palette.
  • Format4BppIndexed Given X and Y coordinates the byte containing the pixel data is calculated as Scan0+(y*Stride)+(x/2). The corresponding byte contains two pixels, the upper nibble is the leftmost and the lower nibble is the rightmost of two pixels. The four bits of the upper and lower nibble are used to select the colour from the 16 colour palette.
  • Format1BppIndexed Given the X and Y coordinates, the byte containing the pixel is calculated by Scan0+(y*Stride)+(x/8). The byte contains 8 bits, each bit is one pixel with the leftmost pixel in bit 8 and the rightmost pixel in bit 0. The bits select from the two entry colour palette.

Iterating through the pixels

For pixel formats with one or more bytes per pixel, the formula is simple and can be accomplished by looping through all Y and X values in order. The code in the following listings sets the blue component of a 32 bit per pixel image to 255. In both cases bm is a bitmap previously created.

BitmapData bmd=bm.LockBits(new Rectangle(0, 0, 10, 10), System.Drawing.Imaging.ImageLockMode.ReadOnly, bm.PixelFormat);

int PixelSize=4;

for(int y=0; y<bmd.Height; y++)
{
    byte* row = (byte *)bmd.Scan0+(y*bmd.Stride);
    for(int x = 0; x<bmd.Width; x++)
    {
        row[x * PixelSize] = 255;
    }
}

In VB this operation would be treated a little differently because VB has no knowledge of pointers and requires the use of the marshal class to access unmanaged data.

Dim x As Integer
Dim y As Integer
Dim PixelSize As Integer = 4

Dim bmd As BitmapData = bm.LockBits(new Rectangle(0, 0, 10, 10), System.Drawing.Imaging.ImageLockMode.ReadOnly, bm.PixelFormat)

For y = 0 To bmd.Height - 1
    For x = 0 To bmd.Width - 1
        Marshal.WriteByte(bmd.Scan0, (bmd.Stride * y) + (4 * x) , 255)
    Next
Next

Sub-byte pixels.

The Format4BppIndexed and Format1BppIndexed pixel formats mentioned earlier both have more than one pixel stored in a byte. In such cases, it's up to you to ensure that changing the data for one pixel does not effect the other pixel or pixels held in that byte.

The method for indexing a 1 bit per pixel image relies on using bitwise logical operations And and Or to reset or set specific bits in the byte. After using the formula shown above for 1 bit per pixel images, the lower 3 bits of the X coordinate is used to select the bit required. The listings below show this process in C# and VB. In both examples bmd is a bitmap data extracted from a 1 bit per pixel image.

C# code uses pointers and requires compiling with unsafe code

byte* p=(byte*)bmd.Scan0.ToPointer();

int index=y*bmd.Stride+(x>>3);

byte mask=(byte)(0x80>>(x&0x7));

if(pixel)
    p[index]|=mask;
else
    p[index]&=(byte)(mask^0xff);

VB code uses the marshal class

Dim mask As Byte = 128 >> (x And 7)

Dim offset As Integer = (y * bmd.Stride) + (x >> 3)

Dim currentPixel As Byte = Marshal.ReadByte(bmd.Scan0, offset)

If pixel = True Then
    Marshal.WriteByte(bmd.Scan0, offset, currentPixel Or mask)
Else
    Marshal.WriteByte(bmd.Scan0, offset, CByte(currentPixel And (mask Xor 255)))
End If

Note, it's quite valid to use the Marshal class from C# code. I used pointers because it offers the best performance.

Accessing individual pixels in a 4 bit per pixel image is handled in a similar manner. The upper and lower nibble of the byte must be dealt with separately and changing the contents of the odd X pixels should not effect the even X pixels. The code below shows how to perform this in C# and VB.

C#

int offset = (y * bmd.Stride) + (x >> 1);

byte currentByte = ((byte *)bmd.Scan0)[offset];

if((x&1) == 1)
{
    currentByte &= 0xF0;
    currentByte |= (byte)(colorIndex & 0x0F);
}
else
{
    currentByte &= 0x0F;
    currentByte |= (byte)(colorIndex << 4);
}

((byte *)bmd.Scan0)[offset]=currentByte;

VB

Dim offset As Integer = (y * bmd.Stride) + (x >> 1)

Dim currentByte As Byte = Marshal.ReadByte(bmd.Scan0, offset)

If (x And 1) = 1 Then
    currentByte = currentByte And &HF0
    currentByte = currentByte Or (colorIndex And &HF)
Else
    currentByte = currentByte And &HF
    currentByte = currentByte Or (colorIndex << 4)
End If

Marshal.WriteByte(bmd.Scan0, offset, currentByte)

Using LockBits and UnlockBits

The LockBits method takes a rectangle which may be the same size or smaller than the image being processed, a PixelFormat which is usually the same as that of the image being processed and a ImageLockMode value that specifies whether the data is read-only, write-only, read-write or a user allocated buffer. This last option cannot be used from C# or VB because the method overload for LockBits that specifies a user buffer is not included in the GDI+ managed wrapper.

It is very important that when all operations are complete the BitmapData is put back into the bitmap with the UnlockBits method. The snippet of code below illustrates this.

Dim bmd As BitmapData = bm.LockBits(New Rectangle(0, 0, 10, 10), ImageLockMode.ReadWrite, bm.PixelFormat)

    ' do operations here

bm.UnlockBits(bmd)

Summary

That just about covers the aspects of accessing the most popular and most difficult pixel formats directly. Using these techniques instead of the GetPixel and SetPixel methods provided by Bitmap will show a marked performance boost to your image processing and image format conversion routines.

这篇关于C#设置像素颜色的更快方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆