手动加载位图

时间:2015-02-21 17:39:58

标签: c# bitmap bmp

我正在尝试编写一些C#代码,这些代码将命名为磁盘上的.bmp。 为此,我一直关注维基百科页面:BMP File Format

所以我创建了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


    }

这似乎也有效..所以现在我有一些关于disp的重要信息 铺设那张照片。

biBitCount = bits per pixel
biHeight = height in pixel
biWidth  = width in pixel
bfOffBit = Pixel array offset

所以我认为这是一个聪明的人。 我会开始将它们复制到我创建的包含3个字节的结构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()创建的Image几乎只是黑色。所以我读错了像素,但我无法确定为什么?

我用来测试它的图像是Experiment image

我得到的是:My image

由于 感谢任何帮助

2 个答案:

答案 0 :(得分:2)

目前尚不清楚为什么你会觉得位图中的像素以你描述的方式存储。每六个像素数据字节之间填充两个字节?

扫描线中的像素之间根本没有填充。如果你有一个24bpp的图像,那么像素一次存储三个字节,在一个连续的三字节像素数组中。

也许你会混淆每行填充?整个扫描线仅在最后填充到4个字节(32位)的倍数。通常,处理此问题的方法是计算位图的“步幅”,这只是单个扫描线的字节长度的另一个字。然后,您将遍历位图中的xy坐标,根据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
    }
}

当然,您必须查看位图标头信息,以正确确定实际的每像素位数(在这种情况下为每像素3个字节,因此为ibPixel += 3)和组件顺序(像素字节并不总是红绿蓝......另一种常见的顺序是蓝绿红色。


所有这一切,还有一个问题,为什么你要试图实现这一点。 .NET已经有各种类,允许您从文件加载位图图像,甚至可以在需要时访问原始像素数据。为什么重新发明轮子?

如果仅仅作为学术练习,那很好,但如果这是真实世界的代码,我强烈建议从.NET中现有的位图支持类开始。如果你需要直接访问像素数据,你仍然需要关注像实际的像素格式,步幅等等,但是位图支持类提供了比尝试直接解释文件数据本身更方便的访问权限。 ,并且在任何情况下都为您提供了一个完全可用的图像对象。

答案 1 :(得分:0)

感谢@Peter_Duniho以正确的方式指出了我,我对他为我清理的填充感到困惑。

我的错误是非常简单的错误,在fs.Read()中,我没有告诉它读取图像的大小,而是让它读取偏移量。