有四种BMP位图格式:
其中第一种现在已经很少见了,后两种和第二种是基本兼容的,但也很少使用。
位图可能是1/4/8/16/24/32位色
为了简便起见,这里只支持最简单、最常用的Win3.0格式24位位图,Windows画图就可以保存为这种格式。
BMP有一点和通常的图形编程非常不一样,它的Y轴是由下向上的,因此在画出图形的时候要翻转过来。
注意这里还有个坑,BMP每行之间必须是4字节对齐的,所以不要直接biWidth*3,需要进行对齐修正。
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;
}
}
}
}
}