有四种BMP位图格式:
其中第一种现在已经很少见了,后两种和第二种是基本兼容的,但也很少使用。
位图可能是1/4/8/16/24/32位色
为了简便起见,这里只支持最简单、最常用的Win3.0格式24位位图,Windows画图就可以保存为这种格式。
BMP有一点和通常的图形编程非常不一样,它的Y轴是由下向上的,因此在画出图形的时候要翻转过来。
注意这里还有个坑,BMP每行之间必须是4字节对齐的,所以不要直接biWidth*3,需要进行对齐修正。
<code class="language-C#">using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.IO; using System.Runtime.InteropServices; using System.Text; using System.Windows.Forms; namespace bmpreader { public partial class Form1 : Form { public Form1() { InitializeComponent(); } // 只支持最简单、最常用的Win3.0格式24位位图。 public struct BitmapFileHeader { public ushort bfType; // 0x4D42 public uint bfSize; // 整个文件的大小 public ushort bfReserved1; // 保留为0 public ushort bfReserved2; // 保留为0 public uint bfOffsetBits; // 位块的偏移量 public BitmapFileHeader(byte[] src, int off) { bfType = BitConverter.ToUInt16(src, off); bfSize = BitConverter.ToUInt32(src, off + 2); bfReserved1 = BitConverter.ToUInt16(src, off + 6); bfReserved2 = BitConverter.ToUInt16(src, off + 8); bfOffsetBits = BitConverter.ToUInt32(src, off + 10); } } public enum BICompression : uint { BI_RGB = 0, // 不压缩 BI_RLE8 = 1, // 8位运行长度编码 BI_RLE4 = 2, // 4位运行长度编码 BI_BITFIELDS = 3 // 使用颜色位屏蔽 } public struct BitmapInfoHeader { public uint biSize; // 结构的大小>=40 public int biWidth; // 图像宽度(像素) public int biHeight; // 图像高度(像素) public ushort biPlanes; // = 1 public ushort biBitCount; // 每像素的位数(1,4,8,16,24,32) public BICompression biCompression; // 压缩代码 public uint biSizeImage; // 图像的字节数 public int biXPelsPerMeter; // 横向分辨率 public int biYPelsPerMeter; // 纵向分辨率 public uint biClrUsed; // 使用的颜色数 public uint biClrImportant; // 重要颜色数 public BitmapInfoHeader(byte[] src, int off) { biSize = BitConverter.ToUInt32(src, off); biWidth = BitConverter.ToInt32(src, off + 4); biHeight = BitConverter.ToInt32(src, off + 8); biPlanes = BitConverter.ToUInt16(src, off + 12); biBitCount = BitConverter.ToUInt16(src, off + 14); biCompression = (BICompression)BitConverter.ToUInt32(src, off + 16); biSizeImage = BitConverter.ToUInt32(src, off + 20); biXPelsPerMeter = BitConverter.ToInt32(src, off + 24); biYPelsPerMeter = BitConverter.ToInt32(src, off + 28); biClrUsed = BitConverter.ToUInt32(src, off + 32); biClrImportant = BitConverter.ToUInt32(src, off + 36); } } private bool readbmpheader(byte[] bytes, out BitmapFileHeader bfh, out BitmapInfoHeader bih) { if (bytes.Length < 54) { bfh = new BitmapFileHeader(); bih = new BitmapInfoHeader(); msgout("无效文件:文件长度小于54字节\r\n"); return false; } else { bfh = new BitmapFileHeader(bytes, 0); bih = new BitmapInfoHeader(bytes, 14); // 第一头部 msgout("BitmapFileHeader\r\n"); msgout(".bfType=0x{0:X}\r\n", bfh.bfType); msgout(".bfSize={0}\r\n", bfh.bfSize); msgout(".bfReserved1={0}\r\n", bfh.bfReserved1); msgout(".bfReserved2={0}\r\n", bfh.bfReserved2); msgout(".bfOffsetBits={0}\r\n", bfh.bfOffsetBits); // 第二头部 msgout("BitmapInfoHeader\r\n"); msgout(".biSize={0}\r\n", bih.biSize); msgout(".biWidth={0}\r\n", bih.biWidth); msgout(".biHeight={0}\r\n", bih.biHeight); msgout(".biPlanes={0}\r\n", bih.biPlanes); msgout(".biBitCount={0}\r\n", bih.biBitCount); msgout(".biCompression={0} ({1})\r\n", (uint)bih.biCompression, bih.biCompression); msgout(".biSizeImage={0}\r\n", bih.biSizeImage); msgout(".biXPelsPerMeter={0}\r\n", bih.biXPelsPerMeter); msgout(".biYPelsPerMeter={0}\r\n", bih.biYPelsPerMeter); msgout(".biClrUsed={0}\r\n", bih.biClrUsed); msgout(".biClrImportant={0}\r\n", bih.biClrImportant); return true; } } // 返回每行的字节数,注意必须是4字节对齐的 private int getrowlen(int width, int bitcount) { return (width * bitcount + 31) / 32 * 4; } private bool validatebmp(byte[] bytes, BitmapFileHeader bfh, BitmapInfoHeader bih) { // 检验BMP头部的有效性 bool noerror = true; if (bfh.bfType != 0x4D42) { msgout("无效文件头:不是BMP文件\r\n"); return false; } if (bfh.bfReserved1 != 0) { msgout("无效字段:bfReserved1必须为0\r\n"); noerror = false; } if (bfh.bfReserved2 != 0) { msgout("无效字段:bfReserved2必须为0\r\n"); noerror = false; } if (bih.biSize < 40) { msgout("无效文件头:不支持的OS/2文件格式\r\n"); return false; } if (bih.biPlanes != 1) { msgout("无效字段:biPlanes必须为1\r\n"); noerror = false; } if (bih.biBitCount != 24) { msgout("不支持的字段:biBitCount必须为24\r\n"); noerror = false; } if (bih.biCompression != BICompression.BI_RGB) { msgout("不支持的字段:biCompression必须为BI_RGB\r\n"); noerror = false; } // 检验文件大小 // [ [ offset + [ [ rowlen * height ] sizeimage ] size ] length ] long wantsize = getrowlen(bih.biWidth, bih.biBitCount) * bih.biHeight; if (wantsize > bih.biSizeImage) { msgout("文件大小错误:期望图像大小{0}大于biSizeImage\r\n", wantsize); noerror = false; } long wanteof = bfh.bfOffsetBits + bih.biSizeImage; if (wanteof > bfh.bfSize) { msgout("文件大小错误:bfOffsetBits+biSizeImage={0}越过bfSize\r\n", wanteof); noerror = false; } if (bfh.bfSize > bytes.Length) { msgout("文件大小错误:bfSize越过实际文件结尾{0}\r\n", bytes.Length); noerror = false; } return noerror; } private Bitmap read24bmp(byte[] bytes, uint offset, int width, int height) { // 读取24位位图数据 Bitmap bmp = new Bitmap(width, height); int rowlen = getrowlen(width, 24); for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { uint boff = (uint)(offset + rowlen * y + x * 3); Color clr = Color.FromArgb(bytes[boff + 2], bytes[boff + 1], bytes[boff]); bmp.SetPixel(x, height - y - 1, clr); // 翻转Y轴 } } return bmp; } private void msgout(string fmt, params object[] args) { textBox1.Text += string.Format(fmt, args); } byte[] bmpdata; private void button1_Click(object sender, EventArgs e) { OpenFileDialog ofd = new OpenFileDialog(); ofd.Filter = "位图 (*.bmp)|*.bmp|所有文件 (*.*)|*.*||"; if (ofd.ShowDialog() == DialogResult.OK) { textBox1.Text = ""; bmpdata = File.ReadAllBytes(ofd.FileName); BitmapFileHeader bfh; BitmapInfoHeader bih; if (readbmpheader(bmpdata, out bfh, out bih) && validatebmp(bmpdata, bfh, bih)) { msgout("文件有效\r\n"); Bitmap bmp = read24bmp(bmpdata, bfh.bfOffsetBits, bih.biWidth, bih.biHeight); pictureBox1.Image = bmp; } } } } } </code>
[修改于 8年7个月前 - 2016/06/08 01:47:31]
时段 | 个数 |
---|---|
{{f.startingTime}}点 - {{f.endTime}}点 | {{f.fileCount}} |