手动加载位图
Loading bitmap manually
我正在尝试编写一些 C# 代码,它将为磁盘上的 .bmp 命名。
为此,我一直在关注维基百科页面:BMP File Format
所以我创建了 2 个 类 来包含 header。
首先是文件 header :
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 ]
}
这似乎工作得很好。所以继续下一个 header,图片 Header。 :
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 个字节的结构二维数组中。红色、绿色和蓝色。
因此,将文件的所有其余部分读入缓冲区。并且运行一次通过该缓冲区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 的图像,那么像素一次存储三个字节,在一个连续的 three-byte 像素数组中。
也许您混淆了 per-row 填充?整个扫描线 仅在最后 填充为 4 字节(32 位)的倍数。通常,处理此问题的方法是计算位图的 "stride",这只是单个扫描线字节长度的另一种说法。然后,您将在位图中的 x
和 y
坐标上循环,根据 y
值和步幅重新计算每个新行的字节偏移量。
在您引用的维基百科文章中,这在 "Pixel Storage" 的标题下进行了说明。
计算步幅后,实际代码通常如下所示(对于 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
}
}
当然,您必须查看位图 header 信息才能正确确定诸如实际 bits-per-pixel 之类的东西(在这种情况下每个像素 3 个字节,因此 ibPixel += 3
),和组件顺序(像素字节并不总是 red-green-blue...另一个常见顺序是 blue-green-red)。
综上所述,还有一个问题是您为什么要尝试实现这一点。 .NET 已经有各种 类 允许您从文件加载位图图像,甚至可以在需要时访问原始像素数据。为什么要重新发明轮子?
如果只是作为学术练习,那很好,但如果这是用于 real-world 代码,我强烈建议从 .NET 中现有的位图支持 类 之一开始。如果您需要直接访问像素数据,您仍然需要关心实际像素格式、步幅等问题,但位图支持 类 提供了比尝试解释更方便的访问方式直接文件数据本身,并且在任何情况下都会为您提供完全可用的 图像 object。
感谢@Peter_Duniho 向我指出了正确的方法,我对他为我清理的填充感到困惑。
我的错误是非常简单的错误,在 fs.Read() 中我没有告诉它读取图像的大小,而是让它读取偏移量。
我正在尝试编写一些 C# 代码,它将为磁盘上的 .bmp 命名。 为此,我一直在关注维基百科页面:BMP File Format
所以我创建了 2 个 类 来包含 header。 首先是文件 header :
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 ]
}
这似乎工作得很好。所以继续下一个 header,图片 Header。 :
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 个字节的结构二维数组中。红色、绿色和蓝色。 因此,将文件的所有其余部分读入缓冲区。并且运行一次通过该缓冲区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 的图像,那么像素一次存储三个字节,在一个连续的 three-byte 像素数组中。
也许您混淆了 per-row 填充?整个扫描线 仅在最后 填充为 4 字节(32 位)的倍数。通常,处理此问题的方法是计算位图的 "stride",这只是单个扫描线字节长度的另一种说法。然后,您将在位图中的 x
和 y
坐标上循环,根据 y
值和步幅重新计算每个新行的字节偏移量。
在您引用的维基百科文章中,这在 "Pixel Storage" 的标题下进行了说明。
计算步幅后,实际代码通常如下所示(对于 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
}
}
当然,您必须查看位图 header 信息才能正确确定诸如实际 bits-per-pixel 之类的东西(在这种情况下每个像素 3 个字节,因此 ibPixel += 3
),和组件顺序(像素字节并不总是 red-green-blue...另一个常见顺序是 blue-green-red)。
综上所述,还有一个问题是您为什么要尝试实现这一点。 .NET 已经有各种 类 允许您从文件加载位图图像,甚至可以在需要时访问原始像素数据。为什么要重新发明轮子?
如果只是作为学术练习,那很好,但如果这是用于 real-world 代码,我强烈建议从 .NET 中现有的位图支持 类 之一开始。如果您需要直接访问像素数据,您仍然需要关心实际像素格式、步幅等问题,但位图支持 类 提供了比尝试解释更方便的访问方式直接文件数据本身,并且在任何情况下都会为您提供完全可用的 图像 object。
感谢@Peter_Duniho 向我指出了正确的方法,我对他为我清理的填充感到困惑。
我的错误是非常简单的错误,在 fs.Read() 中我没有告诉它读取图像的大小,而是让它读取偏移量。