C#纯手工解析BMP文件
acmilan2016/06/07软件综合 IP:四川

有四种BMP位图格式:

  1. OS/2设备无关位图(BITMAPFILEHEADER+BITMAPCOREHEADER)
  2. Win3.0设备无关位图(BITMAPFILEHEADER+BITMAPINFOHEADER)
  3. Win95设备无关位图(BITMAPFILEHEADER+BITMAPV4HEADER)
  4. Win98设备无关位图(BITMAPFILEHEADER+BITMAPV5HEADER)

其中第一种现在已经很少见了,后两种和第二种是基本兼容的,但也很少使用。

位图可能是1/4/8/16/24/32位色

  • 1/4/8需要颜色表
  • 4/8有可能是运行长度编码
  • 16/32可能具有颜色位屏蔽

为了简便起见,这里只支持最简单、最常用的Win3.0格式24位位图,Windows画图就可以保存为这种格式。

BMP有一点和通常的图形编程非常不一样,它的Y轴是由下向上的,因此在画出图形的时候要翻转过来。

注意这里还有个坑,BMP每行之间必须是4字节对齐的,所以不要直接biWidth*3,需要进行对齐修正。

bmpreader.png

<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]

来自:计算机科学 / 软件综合
0
已屏蔽 原因:{{ notice.reason }}已屏蔽
{{notice.noticeContent}}
~~空空如也

想参与大家的讨论?现在就 登录 或者 注册

所属专业
所属分类
上级专业
同级专业
acmilan
进士 学者 笔友
文章
461
回复
2934
学术分
4
2009/05/30注册,5年10个月前活动
暂无简介
主体类型:个人
所属领域:无
认证方式:邮箱
IP归属地:未同步
文件下载
加载中...
{{errorInfo}}
{{downloadWarning}}
你在 {{downloadTime}} 下载过当前文件。
文件名称:{{resource.defaultFile.name}}
下载次数:{{resource.hits}}
上传用户:{{uploader.username}}
所需积分:{{costScores}},{{holdScores}}下载当前附件免费{{description}}
积分不足,去充值
文件已丢失

当前账号的附件下载数量限制如下:
时段 个数
{{f.startingTime}}点 - {{f.endTime}}点 {{f.fileCount}}
视频暂不能访问,请登录试试
仅供内部学术交流或培训使用,请先保存到本地。本内容不代表科创观点,未经原作者同意,请勿转载。
音频暂不能访问,请登录试试
支持的图片格式:jpg, jpeg, png
插入公式
评论控制
加载中...
文号:{{pid}}
投诉或举报
加载中...
{{tip}}
请选择违规类型:
{{reason.type}}

空空如也

加载中...
详情
详情
推送到专栏从专栏移除
设为匿名取消匿名
查看作者
回复
只看作者
加入收藏取消收藏
收藏
取消收藏
折叠回复
置顶取消置顶
评学术分
鼓励
设为精选取消精选
管理提醒
编辑
通过审核
评论控制
退修或删除
历史版本
违规记录
投诉或举报
加入黑名单移除黑名单
查看IP
{{format('YYYY/MM/DD HH:mm:ss', toc)}}