【问题标题】:Loading bitmap manually手动加载位图
【发布时间】:2015-02-21 17:39:58
【问题描述】:

我正在尝试编写一些 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


    }

这似乎也有效..所以现在我有一些重要的信息可以显示 放那张照片。

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() 创建的图像几乎只是黑色的。所以我读错了像素,但我无法确定为什么?

我用来测试的图片是

我得到的是:

谢谢 任何帮助表示赞赏

【问题讨论】:

    标签: c# bitmap bmp


    【解决方案1】:

    不清楚为什么您会认为位图中的像素是以您描述的方式存储的。每六个字节的像素数据之间有两个字节的填充?

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

    也许您对 per-row 填充感到困惑?整个扫描线仅在最后填充为 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 中现有的位图支持类之一开始。如果您需要直接访问像素数据,您仍然需要关注实际像素格式、步幅等问题,但位图支持类提供了比尝试直接解释文件数据本身更方便的访问,并且在任何情况下也为您提供一个完全可用的 image 对象。

    【讨论】:

    • 嗨@Peter。由于学术原因,我为此工作,我仍然不确定这个填充。据我所知,00 填充将计入总文件大小,对吗?但是当我这样做时:totalBytes = Height x Width x BytesPerPixel 它等于:bfSize - ((40) iHeader size + (14) fHeader size)
    • @MrSykkox:宽度是多少?如果例如对于 24bpp 图像,width * 3 已经是 4 的倍数(即width 本身是 4 的倍数),则根本不需要填充。
    • 现在我尝试使用 60x60 的图片
    • @MrSykkox:60 / 4 = 15,完全正确。你去吧。该文件中不会出现任何填充,因为 60 是 4 的倍数。
    • 所以我编辑了我的代码,不跳过字节进行填充,2秒我会改变问题。
    【解决方案2】:

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

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-06-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多