Loading bitmap manually

自闭症网瘾萝莉.ら 提交于 2020-01-24 02:04:27

问题


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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!