我正在尝试编写一些C#代码,该代码将使用位于磁盘上的.bmp命名。为此,我一直在关注Wikipedia页面:BMP文件格式
所以我创建了2个类来包含标题。首先是文件头:
class BMPFileHeader
{
public BMPFileHeader(Byte[] headerBytes)
{
// Position
int offset = 0;
// Read 2 byes
bfType = ((char)headerBytes[0]).ToString();
bfType += ((char)headerBytes[1]).ToString();
offset = offset + 2;
// Read 4 bytes to uint32
bfSize = BitConverter.ToUInt32(headerBytes, offset);
offset = offset + sizeof(UInt32);
// Read 2 bytes to uint16
bfReserved1 = BitConverter.ToUInt16(headerBytes, offset);
offset = offset + sizeof(UInt16);
// Read 2 bytes to uint16
bfReserved2 = BitConverter.ToUInt16(headerBytes, offset);
offset = offset + sizeof(UInt16);
// Read 4 bytes to uint32
bfOffBits = BitConverter.ToUInt32(headerBytes, offset);
offset = offset + sizeof(UInt32);
}
public string bfType; // Ascii characters "BM"
public UInt32 bfSize; // The size of file in bytes
public UInt16 bfReserved1; // Unused, must be zero
public UInt16 bfReserved2; // Same ^^
public UInt32 bfOffBits; // Pixel offset [ where pixel array starts ]
}
这似乎工作得很好。因此,转到下一个标题,即图像标题。:
class BMPImageHeader
{
public BMPImageHeader(Byte[] headerBytes)
{
// Position
int offset = 0;
biSize = BitConverter.ToUInt32(headerBytes, offset);
offset = offset + sizeof(UInt32);
biWidth = BitConverter.ToUInt32(headerBytes, offset);
offset = offset + sizeof(UInt32);
biHeight = BitConverter.ToUInt32(headerBytes, offset);
offset = offset + sizeof(UInt32);
biPlanes = BitConverter.ToUInt16(headerBytes, offset);
offset = offset + sizeof(UInt16);
biBitCount = BitConverter.ToUInt16(headerBytes, offset);
offset = offset + sizeof(UInt16);
biCompression = BitConverter.ToUInt32(headerBytes, offset);
offset = offset + sizeof(UInt32);
biSizeImage = BitConverter.ToUInt32(headerBytes, offset);
offset = offset + sizeof(UInt32);
XPelsPerMeter = BitConverter.ToUInt32(headerBytes, offset);
offset = offset + sizeof(UInt32);
YPelsPerMeter = BitConverter.ToUInt32(headerBytes, offset);
offset = offset + sizeof(UInt32);
biClrUsed = BitConverter.ToUInt32(headerBytes, offset);
offset = offset + sizeof(UInt32);
biClrImportant = BitConverter.ToUInt32(headerBytes, offset);
offset = offset + sizeof(UInt32);
}
public UInt32 biSize; // Size of header, must be least 40
public UInt32 biWidth; // Image width in pixels
public UInt32 biHeight; // Image height in pixels
public UInt16 biPlanes; // Must be 1
public UInt16 biBitCount; // Bits per pixels.. 1..4..8..16..32
public UInt32 biCompression; // 0 No compression
public UInt32 biSizeImage; // Image size, may be zer for uncompressed
public UInt32 XPelsPerMeter; // Preferred resolution in pixels per meter
public UInt32 YPelsPerMeter; // Same ^^
public UInt32 biClrUsed; // Number color map entries
public UInt32 biClrImportant; // Number of significant colors
}
这似乎也可行。.所以现在我有一些重要的信息来显示这张照片。
biBitCount = bits per pixel
biHeight = height in pixel
biWidth = width in pixel
bfOffBit = Pixel array offset
因此,作为一个聪明的人,我以为我是。我将开始将它们复制到我创建的包含3个字节的struct 2D数组中。红色,绿色和蓝色。因此,将文件的所有其余部分读取到缓冲区中。然后一次通过该缓冲区3个字节,并添加一个由这3个字节组成的像素。
public ImageManipulation(string _name, string _imageLocation)
{
// Set name
Name = _name;
// Initialize classes
//fHeader = new BMPFileHeader();
//iHeader = new BMPImageHeader();
if (File.Exists(_imageLocation))
{
int offset = 0;
Byte[] fsBuffer;
FileStream fs = File.Open(_imageLocation, FileMode.Open, FileAccess.Read);
// Start by reading file header..
fsBuffer = new Byte[14]; // size 40 bytes
fs.Read(fsBuffer, 0, 14);
fHeader = new BMPFileHeader(fsBuffer);
// Then image header, 40 bytes
fsBuffer = new Byte[40];
fs.Read(fsBuffer, 0, 40);
iHeader = new BMPImageHeader(fsBuffer);
// Apply pixels
Pixels = new RGBPixel[iHeader.biHeight, iHeader.biWidth];
// How many bytes per pixel
int bpi = iHeader.biBitCount/8;
// Read pixel array
long totalBytes = iHeader.biWidth*iHeader.biHeight*bpi;
if (totalBytes == iHeader.biSizeImage) ;
if (iHeader.biSizeImage == ( fHeader.bfSize - (iHeader.biSize + 14))) ;
// Create butter, read data
fsBuffer = new Byte[iHeader.biWidth*iHeader.biHeight*bpi];
fs.Read(fsBuffer, 0, (int)fHeader.bfOffBits);
int RowSize = ((iHeader.biBitCount *(int) iHeader.biWidth + 31) / 32) * 4;
int x, y;
x = y = 0;
long i;
int lcounter = 0;
// This reads 3 bytes a time
for (i = 0; i < totalBytes; i = i + 3)
{
// Read 3 bytes
Pixels[ (iHeader.biHeight-1) - y, x].RED = fsBuffer[i];
Pixels[ (iHeader.biHeight-1) - y, x].GREEN = fsBuffer[i + 1];
Pixels[ (iHeader.biHeight-1) - y, x].BLUE = fsBuffer[i + 2];
// Update position in array
x++;
if (x == iHeader.biWidth)
{
y++;
x = 0;
}
}
}
}
我在运行和编译此代码时没有任何错误,但是后来我用Bitmap.setPixel()创建的图像几乎只是黑色的。所以我以某种方式读取像素错误,但我无法确定原因??
我用来测试的图像是
我得到的是:
谢谢任何帮助表示赞赏
目前尚不清楚为什么会给人以位图中的像素以您描述的方式存储的印象。每六个字节的像素数据之间有两个填充字节?
扫描线内的像素之间完全没有填充。如果您有一个24bpp的图像,则像素将以三个字节像素的单个连续数组的形式一次存储三个字节。
也许您混淆了每行填充?整个扫描线仅在最后填充4字节(32位)的倍数。通常,处理此问题的方式是计算位图的“跨度”,这只是表示单个扫描行的字节长度的另一个词。然后,您将在位图中的x
和y
坐标上循环,根据y
值和跨度重新计算每个新行的字节偏移。
在您引用的Wikipedia文章中,此问题在“像素存储”标题下解决。
计算出跨度后,实际代码通常看起来像这样(24bpp):
byte[] buffer = ...;
for (int y = 0, ibRow = 0; y < height; y++, ibRow += stride)
{
for (int x = 0, ibPixel = ibRow; x < width; x++, ibPixel += 3)
{
byte red = buffer[ibPixel],
green = buffer[ibPixel + 1],
blue = buffer[ibPixel + 2];
// do something with pixel data
}
}
自然,您必须查看位图标头信息才能正确确定诸如实际的每像素位数(在这种情况下,每像素3个字节,因此为ibPixel += 3
)和组件顺序(像素字节并不总是红绿色)。 -blue ...另一个常见的顺序是blue-green-red)。
说了这么多,还有一个问题,就是为什么您要尝试完全实现这一点。.NET已经具有各种类,这些类使您可以从文件加载位图图像,甚至在需要时甚至可以访问原始像素数据。为什么要重新发明轮子?
如果只是作为学术练习,那很好,但是如果这是用于真实世界的代码,我强烈建议您从.NET中现有的位图支持类之一开始。如果需要直接访问像素数据,则仍然需要考虑实际像素格式,跨度等问题,但是位图支持类提供了比尝试直接解释文件数据本身更方便的访问方式,并且在任何情况下都可以为您提供完全可用的图像对象。
本文收集自互联网,转载请注明来源。
如有侵权,请联系[email protected] 删除。
我来说两句