如何使用 C# 将 BitmapData 复制到字节数组中?
How can I copy BitmapData into Byte array using C#?
我想将 BitmapData 复制到 byte[],但我在数组中间(索引 6 和 7)得到了不存在的零。我做错了什么?
Bitmap bt = new Bitmap(2, 2, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
for(int ii = 0; ii < bt.Width; ii++)
for(int jj = 0; jj < bt.Height; jj++)
{
int tempVal = (ii + jj * 2)*85;
bt.SetPixel(ii, jj, System.Drawing.Color.FromArgb(tempVal, tempVal, tempVal));
}
Rectangle rect = new Rectangle(0,0,bt.Width, bt.Height);
System.Drawing.Imaging.BitmapData btData = bt.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, bt.PixelFormat);
IntPtr ptr = btData.Scan0;
int bytes = bt.Width * bt.Height * 3;
byte[] rgbValues = new byte[bytes];
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);
bt.UnlockBits(btData);
for (var ii = 0; ii < bytes; ii++)
System.Diagnostics.Debug.WriteLine(rgbValues[ii]);
//bt.Save("test.png");
这些零是填充,因为您使用 Format24bppRgb
格式,每个像素 3 个字节,因此图像中每行的末尾都有一个填充。 BitmapData.Stride
属性 returns 一行在内存中的大小。对于自上而下的图像,这是一个正值,对于自下而上的图像,这是一个负值。对于 .NET 内存位图,stride 始终可以除以 4。
所以如果你想使用托管字节数组,你可以这样做:
byte[] data = new byte[Math.Abs(bitmapData.Stride * bitmapData.Height)];
Marshal.Copy(bitmapData.Scan0, data, 0, data.Length);
或者,如果您使用不安全的代码,您可以像这样扫描这些行:
unsafe
{
byte* line = (byte*)bitmapData.Scan0;
for (int y = 0; y < data.Height; y++)
{
for (int x = 0; x < data.Width; x++)
{
byte* pos = line + x * 3;
int pixel = Color.FromArgb(pos[0], pos[1], pos[2]).ToArgb();
// do whatever
}
line += data.Stride;
}
}
这是设计使然,因为位图像素阵列格式需要填充每一行的起始偏移量以指向一个地址,该地址是 4 的倍数。
来自Wikipedia
出于文件存储的目的,只有每行的大小必须是 4 字节的倍数,而文件偏移量可以是任意的。 [5] Width=1 的 24 位位图每行有 3 个字节的数据(蓝色、绿色、红色)和 1 个字节的填充,而 Width=2 有 2 个字节的填充padding,Width=3 会有 3 个字节的填充,而 Width=4 根本没有任何填充。
顺便说一句,您的字节数计算似乎不正确,根据the documentation,应该是:
bytes = Math.Abs(btData.Stride) * bt.Height;
我想将 BitmapData 复制到 byte[],但我在数组中间(索引 6 和 7)得到了不存在的零。我做错了什么?
Bitmap bt = new Bitmap(2, 2, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
for(int ii = 0; ii < bt.Width; ii++)
for(int jj = 0; jj < bt.Height; jj++)
{
int tempVal = (ii + jj * 2)*85;
bt.SetPixel(ii, jj, System.Drawing.Color.FromArgb(tempVal, tempVal, tempVal));
}
Rectangle rect = new Rectangle(0,0,bt.Width, bt.Height);
System.Drawing.Imaging.BitmapData btData = bt.LockBits(rect, System.Drawing.Imaging.ImageLockMode.ReadOnly, bt.PixelFormat);
IntPtr ptr = btData.Scan0;
int bytes = bt.Width * bt.Height * 3;
byte[] rgbValues = new byte[bytes];
System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes);
bt.UnlockBits(btData);
for (var ii = 0; ii < bytes; ii++)
System.Diagnostics.Debug.WriteLine(rgbValues[ii]);
//bt.Save("test.png");
这些零是填充,因为您使用 Format24bppRgb
格式,每个像素 3 个字节,因此图像中每行的末尾都有一个填充。 BitmapData.Stride
属性 returns 一行在内存中的大小。对于自上而下的图像,这是一个正值,对于自下而上的图像,这是一个负值。对于 .NET 内存位图,stride 始终可以除以 4。
所以如果你想使用托管字节数组,你可以这样做:
byte[] data = new byte[Math.Abs(bitmapData.Stride * bitmapData.Height)];
Marshal.Copy(bitmapData.Scan0, data, 0, data.Length);
或者,如果您使用不安全的代码,您可以像这样扫描这些行:
unsafe
{
byte* line = (byte*)bitmapData.Scan0;
for (int y = 0; y < data.Height; y++)
{
for (int x = 0; x < data.Width; x++)
{
byte* pos = line + x * 3;
int pixel = Color.FromArgb(pos[0], pos[1], pos[2]).ToArgb();
// do whatever
}
line += data.Stride;
}
}
这是设计使然,因为位图像素阵列格式需要填充每一行的起始偏移量以指向一个地址,该地址是 4 的倍数。
来自Wikipedia 出于文件存储的目的,只有每行的大小必须是 4 字节的倍数,而文件偏移量可以是任意的。 [5] Width=1 的 24 位位图每行有 3 个字节的数据(蓝色、绿色、红色)和 1 个字节的填充,而 Width=2 有 2 个字节的填充padding,Width=3 会有 3 个字节的填充,而 Width=4 根本没有任何填充。
顺便说一句,您的字节数计算似乎不正确,根据the documentation,应该是:
bytes = Math.Abs(btData.Stride) * bt.Height;