C#使用位图锁位处理bmp图像。正在尝试更改所有字节,但其中一些字节在保存后的值仍为0。为什么?
我需要使用带锁位的图像处理,而不是GetPixel/SetPixel,以减少处理时间但最终它不会保存所有更改。 重现问题的步骤:C#使用位图锁位处理bmp图像。正在尝试更改所有字节,但其中一些字节在保存后的值仍为0。为什么?,c#,bitmap,bmp,.net-4.6,lockbits,C#,Bitmap,Bmp,.net 4.6,Lockbits,我需要使用带锁位的图像处理,而不是GetPixel/SetPixel,以减少处理时间但最终它不会保存所有更改。 重现问题的步骤: 读取初始bmp文件中的所有字节 更改所有这些字节并将其保存到新文件 读取保存的文件并将其数据与您试图保存的数据进行比较 期望值:他们是平等的,但实际上不是 我注意到(参见输出): 所有错误的值都等于0 错误值的索引如下:x1,x1+1,x2,x2+1 对于某些文件,它可以按预期工作 还有一件让人困惑的事情:我的初始测试文件是灰色的,但在HexEditor中,在
- 读取初始bmp文件中的所有字节李>
- 更改所有这些字节并将其保存到新文件李>
- 读取保存的文件并将其数据与您试图保存的数据进行比较
- 所有错误的值都等于0
- 错误值的索引如下:x1,x1+1,x2,x2+1
- 对于某些文件,它可以按预期工作
MyGetByteArrayByMagefile
和UpdateAllBytes
的代码直接来自msdn文章(方法MakeMoreBlue
),但有一些小的变化:硬编码像素格式被替换,每个字节都在变化(而不是每6个字节)
程序.cs的内容
:
using System;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
namespace Test
{
class Program
{
const int MagicNumber = 43;
static void Main(string[] args)
{
//Arrange
string containerFilePath = "d:/test-images/initial-5.bmp";
Bitmap containerImage = new Bitmap(containerFilePath);
//Act
Bitmap resultImage = UpdateAllBytes(containerImage);
string resultFilePath = "d:/test-result-images/result-5.bmp";
resultImage.Save(resultFilePath, ImageFormat.Bmp);
//Assert
var savedImage = new Bitmap(resultFilePath);
byte[] actualBytes = savedImage.MyGetByteArrayByImageFile(ImageLockMode.ReadOnly).Item1;
int count = 0;
for (int i = 0; i < actualBytes.Length; i++)
{
if (actualBytes[i] != MagicNumber)
{
count++;
Debug.WriteLine($"Index: {i}. Expected value: {MagicNumber}, but was: {actualBytes[i]};");
}
}
Debug.WriteLine($"Amount of different bytes: {count}");
}
private static Bitmap UpdateAllBytes(Bitmap bitmap)
{
Tuple<byte[], BitmapData> containerTuple = bitmap.MyGetByteArrayByImageFile(ImageLockMode.ReadWrite);
byte[] bytes = containerTuple.Item1;
BitmapData bitmapData = containerTuple.Item2;
// Manipulate the bitmap, such as changing all the values of every pixel in the bitmap
for (int i = 0; i < bytes.Length; i++)
{
bytes[i] = MagicNumber;
}
// Copy the RGB values back to the bitmap
Marshal.Copy(bytes, 0, bitmapData.Scan0, bytes.Length);
// Unlock the bits.
bitmap.UnlockBits(bitmapData);
return bitmap;
}
}
}
using System;
using System.Drawing;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
namespace Test
{
public static class ImageExtensions
{
public static Tuple<byte[], BitmapData> MyGetByteArrayByImageFile(this Bitmap bmp, ImageLockMode imageLockMode = ImageLockMode.ReadWrite)
{
// Specify a pixel format.
PixelFormat pxf = bmp.PixelFormat;//PixelFormat.Format24bppRgb;
// Lock the bitmap's bits.
Rectangle rect = new Rectangle(0, 0, bmp.Width, bmp.Height);
BitmapData bmpData = bmp.LockBits(rect, imageLockMode, pxf);
// Get the address of the first line.
IntPtr ptr = bmpData.Scan0;
// Declare an array to hold the bytes of the bitmap.
// int numBytes = bmp.Width * bmp.Height * 3;
int numBytes = bmpData.Stride * bmp.Height;
byte[] rgbValues = new byte[numBytes];
// Copy the RGB values into the array.
Marshal.Copy(ptr, rgbValues, 0, numBytes);
return new Tuple<byte[], BitmapData>(rgbValues, bmpData);
}
}
}
输出示例:
Index: 162. Expected value: 43, but was: 0;
Index: 163. Expected value: 43, but was: 0;
Index: 326. Expected value: 43, but was: 0;
Index: 327. Expected value: 43, but was: 0;
Index: 490. Expected value: 43, but was: 0;
Index: 491. Expected value: 43, but was: 0;
Index: 654. Expected value: 43, but was: 0;
...
Index: 3606. Expected value: 43, but was: 0;
Index: 3607. Expected value: 43, but was: 0;
Index: 3770. Expected value: 43, but was: 0;
Index: 3771. Expected value: 43, but was: 0;
Amount of different bytes: 46
如果有人能解释为什么会发生这种情况以及如何解决,我将不胜感激。多谢各位
解决方案 基于此,我更改了代码: 在文件
Program.cs
中,我仅将方法updatealBytes
更改为使用新的扩展方法updatebitmappayoladbytes
:
private static Bitmap UpdateAllBytes(Bitmap bitmap)
{
Tuple<byte[], BitmapData> containerTuple = bitmap.MyGetByteArrayByImageFile(ImageLockMode.ReadWrite);
byte[] bytes = containerTuple.Item1;
BitmapData bitmapData = containerTuple.Item2;
for (int i = 0; i < bytes.Length; i++)
{
bytes[i] = MagicNumber;
}
bitmap.UpdateBitmapPayloadBytes(bytes, bitmapData);
return bitmap;
}
此代码通过了图像的测试:
- 8,24,32个基点李>
- 当需要和不需要填充字节时(例如, 宽度=63,bpp=24,宽度=64,bpp=24,…)
在我的任务中,我不必关心我正在更改哪些值,但是如果您需要计算红色、绿色和蓝色值的确切位置,请记住字节是按BGR顺序排列的,如答案中所述 您的问题中唯一缺少的是一个示例输入文件,根据您的输出,我推断它是一个54 x 23像素的24bpp位图 与您设置的值不匹配的字节是填充字节 发件人: 对于宽度为54像素、每像素24位的图像,这将提供
楼层((24*54+31)/32)*4
=楼层(1327/32)*4
=41*4
=164
在本例中,164
是Stride
属性的值,请注意,这比54*3
=162
的原始RGB值多了2
字节
此填充确保每一行(像素行)从4的倍数开始
当我在文件保存后检查原始字节时,在我的机器上,所有字节都与幻数匹配
using (var fs = new FileStream(resultFilePath, FileMode.Open))
{
// Skip the bitmap header
fs.Position = 54;
int rawCount = 0;
int i = 0;
int byteValue;
while ((byteValue = fs.ReadByte()) != -1)
{
if (byteValue != MagicNumber)
{
rawCount++;
Debug.WriteLine($"Index: {i}. Expected value: {MagicNumber}, but was: {byteValue};");
}
}
Debug.WriteLine($"Amount of different bytes: {rawCount}");
}
而使用扩展方法读取字节会产生不同的填充字节(如0
s)
如果我随后将savedImage
保存到一个新的目标,并检查该目标的字节,则填充字节现在是0
我假设这是因为C#/.NET在加载文件时没有实际读取填充字节
这些填充字节始终位于每行的末尾,如果逐像素操作图像,则可以/应该跳过每行的最后0-3字节,具体取决于每像素的位数和宽度,因为它们并不代表图像的有效像素;更好的是,根据图像的像素格式
、宽度
和高度
,精确计算每个像素的红色
、绿色
和蓝色
偏移的位置
不幸的是,您链接到的MSDN示例具有误导性,它们的示例图像的宽度恰好可以被32
除以BitsPerPixel
(即(24*100)/32=75
),因此不需要填充字节,在逐字复制时问题不会显现出来
然而,使用不同维度的源图像可以揭示问题。通过添加6
,他们试图移动到每隔一个像素的蓝色值;字节是按BGR顺序排列的,因此从0
开始就可以了,添加6
将跳过Green0
、Red0
、Blue1
、Green1
、Red1
,并落在Blue2
值上。第一行按预期继续,但是,一旦到达162
处的行尾,+6
不考虑填充字节,填充字节现在减少2,这意味着它现在每隔一个像素更新一次绿色
值,直到它到达下一组填充字节,这将把它移到红色
值,依此类推
Row
Col 0 0 1 2 3 4 5 6 7 8 ... 159 160 161 162 163
B0 G0 R0 B1 G1 R1 B2 G2 R2 B53 G53 R53 P P
1 164 165 166 167 168 169 ...
B54 G54 R54 B55 G56 B56
...
从一个54 x 23像素的白色位图开始,将“每隔6个字节”设置为43
,我们可以看到这一点
for(int counter = 0; counter < bytes.Length; counter+=6)
bytes[counter] = MagicNumber;
for(int计数器=0;计数器
而不是这个
for (var r = 0; r < bitmapData.Height; r++)
{
for (var c = 0; c < bitmapData.Width * 3; c += 6)
{
bytes[r * bitmapData.Stride + c] = MagicNumber;
}
}
for(var r=0;r
对于你的第一个问题,这是一个写得很好的问题:)我无法重现这个问题。我不得不改成常量字节MagicNumber
,并做了一些其他的更改。谢谢你的解释。我已经根据你的答案修改了我的代码,它现在可以正常工作了,所以我将很快用新版本的代码更新这个问题。哇,非常好的答案!
for (var r = 0; r < bitmapData.Height; r++)
{
for (var c = 0; c < bitmapData.Width * 3; c += 6)
{
bytes[r * bitmapData.Stride + c] = MagicNumber;
}
}