问题
I'm trying to write some C# code, which will take an name to a .bmp located on the disk. To that I have been following the wikipedia page : BMP File Format
So i have created 2 classes to contain the headers. Firstly the file 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 ]
}
Which seems to work just fine. So moving on to the next header, the image 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
}
Which also seems to work.. So now i have some important information for disp laying that picture.
biBitCount = bits per pixel
biHeight = height in pixel
biWidth = width in pixel
bfOffBit = Pixel array offset
So as the clever man I thought i were. I would start copy them to a struct 2D array I've created containing 3 bytes. RED,GREEN and BLUE. So read all rest of file to a buffer. And run through that buffer 3 bytes at time, and add a pixel made of theese 3 bytes.
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;
}
}
}
}
I don't get any errors running and compiling this code, but the Image i create afterwards with Bitmap.setPixel() is almost just black. So i read the pixels somehow wrong, but i cannot determine why??
The image i use to test this is
And what i get is this :
Thanks Any helps is appreciated
回答1:
It's not clear why you are under the impression that pixels in a bitmap are stored in the way you describe. Two bytes of padding between every six bytes of pixel data?
There is no padding at all between pixels within a scan line. If you have a 24bpp image, then the pixels are stored three bytes at a time, in a single contiguous array of three-byte pixels.
Perhaps you are confusing the per-row padding? A whole scan line is padding out only at the very end to a multiple of 4 bytes (32 bits). Typically, the way one would handle this is to compute the "stride" of the bitmap, which is just another word for the length in bytes of a single scan line. Then you would loop on the x
and y
coordinates within the bitmap, computing the byte offset for each new row anew based on the y
value and the stride.
In the Wikipedia article you referenced, this is addressed under the heading of "Pixel Storage".
Having computed the stride, the actual code usually looks something like this (for 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
}
}
Naturally, you have to look at the bitmap header information to correctly determine things like the actual bits-per-pixel (3 bytes per pixel in this case, hence the ibPixel += 3
), and the component order (the pixel bytes are not always red-green-blue...another common order is blue-green-red).
All that said, there is also the question of why you are trying to implement this at all. .NET already has various classes that allow you to load a bitmap image from a file, and even to gain access to the raw pixel data if needed. Why reinvent the wheel?
If simply as an academic exercise, that's fine, but if this is for real-world code I'd strongly suggest starting with one of the existing bitmap support classes in .NET. If you need direct access to the pixel data, you'll still need to concern yourself with things like the actual pixel format, stride, etc. but the bitmap support classes provide more convenient access to this than trying to interpret the file data itself directly, and in any case provide you with a fully usable image object as well.
回答2:
Thanks to @Peter_Duniho who pointed me in correct way,i was confused over the padding which he cleared up for me.
My mistake was very simple error, in fs.Read() where instead of telling it to read the size of image, i asked it to read the offset.
来源:https://stackoverflow.com/questions/28648974/loading-bitmap-manually