When performing sequential operations on a Bitmap's color data, the Bitmap.LockBits method can provide a huge increase in performace, since the Bitmap data needs to be loaded in memory just once, as opposed to sequential GetPixel/SetPixel calls: each call will load a partial section of the Bitmap data in memory and then discard it, to repeat the process when these methods are called again.
(当对位图的颜色数据执行顺序操作时, Bitmap.LockBits方法可以显着提高性能,因为与连续的GetPixel / SetPixel调用相比,Bitmap数据仅需要一次加载到内存中:每次调用都将加载一个位图数据在内存中的部分区域,然后将其丢弃,以在再次调用这些方法时重复该过程。)
If a single call to GetPixel/SetPixel is needed instead, these methods may have a performace advantage over Bitmap.LockBits()
.
(如果需要单次调用GetPixel / SetPixel,则这些方法可能比Bitmap.LockBits()
具有性能优势。)
But, in this case, performace is not a factor, in practice. (但是,在这种情况下,实际上,性能不是一个因素。)
How Bitmap.LockBits()
works :
(Bitmap.LockBits()
如何工作 :)
This is the function call:
(这是函数调用:)
public BitmapData LockBits (Rectangle rect, ImageLockMode flags, PixelFormat format);
// VB.Net
Public LockBits (rect As Rectangle, flags As ImageLockMode, format As PixelFormat) As BitmapData
rect As Rectangle
: This parameter specifies the section of the Bitmap data we're interested in;
(rect As Rectangle
:此参数指定我们感兴趣的Bitmap数据部分;)
this section's bytes will be loaded in memory. (该部分的字节将被加载到内存中。)
It can be the whole size of the Bitmap or a smaller section of it. (它可以是位图的整体大小,也可以是其较小部分。)
flags As
ImageLockMode
: Specifies the type of lock to perform.
(flags As
ImageLockMode
:指定要执行的锁定的类型。)
The access to the memory to can be limited to Read or Write, or concurrent Read/Write operations are allowed. (对内存的访问可以限制为读或写,或者允许并发的读/写操作。)
It can be also used to specify - setting ImageLockMode.UserInputBuffer
- that the BitmapData object is provided by the calling code.
(它还可以用于指定-设置ImageLockMode.UserInputBuffer
调用代码提供BitmapData对象。)
The BitmapData
object defines some of the Bitmap properties ( Width
and Height
of the Bitmap, width of the scan line (the Stride
: number of bytes that compose a single line of pixels, represented by the Bitmap.Width
multiplied by the number of bytes per pixel, rounded to a 4-bytes boundary. See the note about the Stride
).
(BitmapData
对象定义一些位图属性(位图的Width
和Height
,扫描线的宽度( Stride
:组成单行像素的字节数,由Bitmap.Width
表示。 Bitmap.Width
乘以每字节的字节数)像素,四舍五入到4个字节的边界。请参见有关Stride
的注释)。)
The BitmapData.Scan0 property is the Pointer ( IntPtr
) to the initial memory location where the Bitmap data is stored.
(BitmapData.Scan0属性是指向初始存储位置的指针( IntPtr
),该位置存储了位图数据。)
This property allows to specify the memory location where a pre-existing Bitmap data buffer is already stored.
(该属性允许指定已经存储了预先存在的位图数据缓冲区的存储位置。)
It becomes useful when Bitmap data is exchanged between processes using Pointers. (当使用指针在进程之间交换位图数据时,它将变得很有用。)
Note that the MSDN documentation about ImageLockMode.UserInputBuffer
is confusing (if not wrong).
(请注意,有关ImageLockMode.UserInputBuffer
的MSDN文档令人困惑(如果没有错)。)
-
format As
PixelFormat
: the format used to describe the Color of a single Pixel. (format As
PixelFormat
:用于描述单个Pixel的颜色的格式。)
It translates, in practice, in the number of bytes used to represent a Color. (实际上,它转换为用于表示颜色的字节数。)
When PixelFormat = Format24bppRgb
, each Color is represented by 3 bytes (RGB values).
(当PixelFormat = Format24bppRgb
,每种颜色都由3个字节(RGB值)表示。)
With PixelFormat.Format32bppArgb
, each Color is represented by 4 bytes (RGB values + Alpha). (使用PixelFormat.Format32bppArgb
,每种颜色都由4个字节(RGB值+ Alpha)表示。)
Indexed formats, as Format8bppIndexed
, specify that each byte value is the index to a Palette entry.
(索引格式(如Format8bppIndexed
)指定每个字节值是Palette条目的索引。)
The Palette
is part of the Bitmap information, except when the pixel format is PixelFormat.Indexed
: in this case, each value is an entry in the System color table. (Palette
是位图信息的一部分,除非像素格式为PixelFormat.Indexed
:在这种情况下,每个值都是系统颜色表中的一个条目。)
The default PixelFormat
of a new Bitmap object, if not specified, is PixelFormat.Format32bppArgb
, or PixelFormat.Canonical
.
(如果未指定,则新Bitmap对象的默认PixelFormat
为PixelFormat.Format32bppArgb
或PixelFormat.Canonical
。)
Important notes about the Stride:
(关于Stride的重要说明:)
As mentioned before, the Stride
(also called scan-line) represents the number of bytes that compose a single line of pixels.
(如前所述, Stride
(也称为扫描线)代表组成一行像素的字节数。)
Because of harware alignment requirements, it's always rounded up to a 4-bytes boundary (an integer number multiple of 4). (由于硬件对齐要求,它总是四舍五入为4个字节的边界(4的整数倍)。)
Stride = [Bitmap Width] * [bytes per Color]
Stride += (Stride Mod 4) * [bytes per Color]
This is one of the reasons why we always work with Bitmaps created with PixelFormat.Format32bppArgb
: the Bitmap's Stride
is always already aligned to the required boundary.
(这就是为什么我们始终使用通过PixelFormat.Format32bppArgb
创建的位图的原因之一:位图的Stride
始终已经与所需边界对齐。)
What if the Bitmap's format is instead PixelFormat.Format24bppRgb
(3 bytes per Color) ?
(如果位图的格式改为 PixelFormat.Format24bppRgb
(每种颜色3个字节)怎么办?)
If the Bitmap's Width
multiplied by the Bytes per Pixels is not a multiple of 4
, the Stride
will be padded with 0
s to fill the gap.
(如果位图的Width
乘以每个像素的字节数不是4
的倍数,则将用0
s填充Stride
来填补空白。)
A Bitmap of size (100 x 100)
will have no padding in both 32 bit and 24 bit formats:
(大小为(100 x 100)
位图在32位和24位格式中都不会填充:)
100 * 3 = 300 : 300 Mod 4 = 0 : Stride = 300
100 * 4 = 400 : 400 Mod 4 = 0 : Stride = 400
It will be different for a Bitmap of size (99 x 100)
:
(大小(99 x 100)
的位图会有所不同:)
99 * 3 = 297 : 297 Mod 4 = 1 : Stride = 297 + ((297 Mod 4) * 3) = 300
99 * 4 = 396 : 396 Mod 4 = 0 : Stride = 396
The Stride
of a 24 bit Bitmap is padded adding 3 bytes (set to 0
) to fill the boundary.
(填充24位位图的Stride
,添加3个字节(设置为0
)以填充边界。)
It's not a problem when we inspect/modify internal values accessing single Pixels by their coordinates, similar to how SetPixel/GetPixel operate: the position of a Pixel will always be found correctly.
(当我们检查/修改内部值以通过其坐标访问单个Pixel时,这不是问题 ,类似于SetPixel / GetPixel的操作方式:总是可以正确找到Pixel的位置。)
Suppose we need to inspect/change a Pixel at position (98, 70)
in a Bitmap of size (99 x 100)
.
(假设我们需要检查/更改大小为(99 x 100)
的位图中位置(98, 70)
98,70)处的Pixel。)
Considering only the bytes per pixel.
(仅考虑每个像素的字节数。)
The pixel position inside the Buffer is: (缓冲区内的像素位置为:)
[Bitmap] = new Bitmap(99, 100, PixelFormat = Format24bppRgb)
[Bytes x pixel] = Image.GetPixelFormatSize([Bitmap].PixelFormat) / 8
[Pixel] = new Point(98, 70)
[Pixel Position] = ([Pixel].Y * [BitmapData.Stride]) + ([Pixel].X * [Bytes x pixel])
[Color] = Color.FromArgb([Pixel Position] + 2, [Pixel Position] + 1, [Pixel Position])
Multiplying the Pixel's vertical position by the width of the scan line, the position inside the buffer will always be correct: the padded size is included in the calculation.
(将像素的垂直位置乘以扫描线的宽度,缓冲区内的位置将始终是正确的:计算中包括填充大小。)
The Pixel Color at the next position, (0, 71)
, will return the expected results:
(下一个位置(0, 71)
的像素颜色将返回预期的结果:)
It will be different when reading color bytes sequentially.
(顺序读取颜色字节时会有所不同。)
The first scan line will return valid results up to the last Pixel (the last 3 bytes): the next 3 bytes will return the value of the bytes used to round the Stride
, all set to 0
.
(第一行扫描将返回有效的结果,直到最后一个像素(最后3个字节):接下来的3个字节将返回用于将“ Stride
取整的字节的值,都设置为<code