1.BMP文件格式
BMP文件格式组成部分:bmp文件头(14个字节) + 位图信息头(40个字节) + 调色板(由颜色索引数决定) + 位图数据(由图像尺寸决定)
位图文件头BITMAPFILEHEADER |
位图信息头BITMAPINFOHEADER |
调色板Palette(可选) |
实际的位图数据ImageDate |
第一部分为位图文件头BITMAPFILEHEADER,是一个结构,其定义如下:
typedef struct tagBITMAPFILEHEADER {
WORD bfType;
DWORD bfSize;
WORD bfReserved1;
WORD bfReserved2;
DWORD bfOffBits;
} BITMAPFILEHEADER;
这个结构的长度是固定的,为14个字节(WORD为无符号16位整数,DWORD为无符号32位整数),各个域的说明如下:
bfType | 指定文件类型,必须是0x424D,即字符串“BM”,也就是说所有.bmp文件的头两个字节都是“BM”。 |
bfSize | 指定文件大小,包括这14个字节。 |
bfReserved1 | 保留字,不用考虑 |
bfReserved2 | 保留字,不用考虑 |
bfOffBits | 为从文件头到实际的位图数据的偏移字节数,前三个部分的长度之和。 |
第二部分为位图信息头BITMAPINFOHEADER,也是一个结构,其定义如下:
typedef struct tagBITMAPINFOHEADER{
DWORD biSize;
LONG biWidth;
LONG biHeight;
WORD biPlanes;
WORD biBitCount
DWORD biCompression;
DWORD biSizeImage;
LONG biXPelsPerMeter;
LONG biYPelsPerMeter;
DWORD biClrUsed;
DWORD biClrImportant;
} BITMAPINFOHEADER;
这个结构的长度是固定的,为40个字节(LONG为32位整数),各个域的说明如下:
biSize | 指定这个结构的长度,为40。 |
biWidth | 指定图象的宽度,单位是像素。 |
biHeight | 指定图象的高度,单位是像素。 |
biPlanes | 必须是1,不用考虑。 |
biBitCount | 指定表示颜色时要用到的位数,常用的值为1(黑白二色图), 4(16色图), 8(256色), 24(真彩色图), 32(带透明通道的真彩色图) |
biCompression | 指定位图是否压缩,有效的值为BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS(都是一些Windows定义好的常量)。一般只使用第一种不压缩的情况,即biCompression为BI_RGB的情况。 |
biSizeImage | 指定实际的位图数据占用的字节数,其实也可以从以下的公式中计算出来: biSizeImage=biWidth’ × biHeight 要注意的是:上述公式中的biWidth’必须是4的整倍数(所以不是biWidth,而是biWidth’,表示大于或等于biWidth的,最接近4的整倍数。举个例子,如果biWidth=240,则biWidth’=240;如果biWidth=241,biWidth’=244)。 如果biCompression为BI_RGB,则该项可能为零 |
biXPelsPerMeter | 指定目标设备的水平分辨率,单位是每米的像素个数。 |
biYPelsPerMeter | 指定目标设备的垂直分辨率,单位同上。 |
biClrUsed | 指定本图象实际用到的颜色数,如果该值为零,则用到的颜色数为2的biBitCount次方。 |
biClrImportant | 指定本图象中重要的颜色数,如果该值为零,则认为所有的颜色都是重要的。 |
第三部分为调色板Palette,当然,这里是对那些需要调色板的位图文件而言的。有些位图,如真彩色图,前面已经讲过,是不需要调色板的,BITMAPINFOHEADER后直接是位图数据。
调色板实际上是一个数组,共有biClrUsed个元素(如果该值为零,则有 2的biBitCount次方 个元素)。数组中每个元素的类型是一个RGBQUAD结构,占4个字节,其定义如下:
typedef struct tagRGBQUAD {
BYTE rgbBlue; //该颜色的蓝色分量
BYTE rgbGreen; //该颜色的绿色分量
BYTE rgbRed; //该颜色的红色分量
BYTE rgbReserved; //保留值
} RGBQUAD;
第四部分就是实际的图像数据了。对于用到调色板的位图,图像数据就是该像素颜在调色板中的索引值。对于真彩色图,图象数据就是实际的R、G、B值。
要注意两点:
(1) 每一行的字节数必须是4的整倍数,如果不是,则需要补齐。这在前面介绍biSizeImage时已经提到了。
(2) 一般来说,.bMP文件的数据从下到上,从左到右的。也就是说,从文件中最先读到的是图像最下面一行的左边第一个像素,然后是左边第二个像素……接下来是倒数第二行左边第一个像素,左边第二个像素……依次类推 ,最后得到的是最上面一行的最右一个象素。
2.C#读写BMP代码
using System;
using System.IO;
namespace Demo_CSharp
{
class BmpUtil
{
/**
* @author mickey
* @category 读取BMP图像
* @param strFileName - [in]BMP文件路径
* ucImgBuf - [out]图象数据
* iWidth - [out]图像宽度
* iHeight - [out]图像高度
* iChannels - [out]图像通道数
* @return 0 - 成功
* 其他 - 失败
* @notes 暂时只支持灰度BMP文件格式
* */
public static int ReadBMP(String strFileName, byte[] ucImgBuf, int[] iWidth, int[] iHeight, int[] iChannels)
{
byte[] bBmp = ReadData(strFileName);
if (bBmp == null)
return -1;
return Bmp2Raw(bBmp, ucImgBuf, iWidth, iHeight, iChannels);
}
/**
* @author mickey
* @category 保存BMP图像
* @param strFileName - [in]BMP文件路径
* ucImgBuf - [in]]图象数据
* iWidth - [in]图像宽度
* iHeight - [in]图像高度
* iChannels - [in]图像通道数
* @return 0 - 成功
* 其他 - 失败
* @notes 暂时只支持灰度BMP文件格式
* */
public static int SaveBMP(String strFileName, byte[] ucImgBuf, int iWidth, int iHeight, int iChannels)
{
int iNewWidth = (iChannels * iWidth + 3) / 4 * 4;
byte[] bBmp = new byte[iNewWidth * iHeight + 1078];
int[] iBmpLen = new int[1];
Raw2Bmp(bBmp, iBmpLen, ucImgBuf, iWidth, iHeight, iChannels);
return SaveData(strFileName, bBmp, iBmpLen[0]);
}
public static int JUnsigned(int x)
{
if (x >= 0)
return x;
else
return (x + 256);
}
/**
* @author mickey
* @category BMP格式图像数据 -> 图像裸数据
* @param pBmp - [in]BMP格式图像数据
* pRaw - [out]图像裸数据
* iWidth - [out]图像宽度
* iHeight - [out]图像高度
* iChannels - [out]图像通道数
* @return 0 - 成功
* 其他 - 失败
* */
public static int Bmp2Raw(byte[] pBmp, byte[] pRaw, int[] iWidth, int[] iHeight, int[] iChannels)
{
int i, X, Y, channels;
byte[] head = new byte[1078];
Array.Copy(pBmp, 0, head, 0, 54);
if (head[0] != 0x42 || head[1] != 0x4D)
return -1;
int head18 = (int)head[18];
int head19 = (int)(head[19] << 8);
int head20 = (int)(head[20] << 8);
int head21 = (int)(head[21] << 8);
int head22 = (int)head[22];
int head23 = (int)(head[23] << 8);
int head24 = (int)(head[24] << 8);
int head25 = (int)(head[25] << 8);
X = JUnsigned(head18) + JUnsigned(head19) + JUnsigned(head20) + JUnsigned(head21);
Y = JUnsigned(head22) + JUnsigned(head23) + JUnsigned(head24) + JUnsigned(head25);
int head28 = (int)(head[28]);
int head29 = (int)(head[29] << 8);
channels = JUnsigned(head28) + JUnsigned(head29);
channels = channels / 8;
//System.out.println("channels="+channels);
iWidth[0] = X;
iHeight[0] = Y;
iChannels[0] = channels;
if (channels == 4)
iChannels[0] = 3;
int iNewWidth = (channels * X + 3) / 4 * 4;
if (channels == 1)
{
for (i = 0; i < Y; i++)
{
Array.Copy(pBmp, 1078 + (Y - 1 - i) * iNewWidth, pRaw, i * X, X);
}
}
else if (channels == 3)
{
for (i = 0; i < Y; i++)
{
Array.Copy(pBmp, 54 + (Y - 1 - i) * iNewWidth, pRaw, i * 3 * X, 3 * X);
}
}
else if (channels == 4)
{
for (i = 0; i < Y; i++)
{
for (int j = 0; j < X; j++)
{
pRaw[i * 3 * X + 3 * j] = pBmp[54 + (Y - 1 - i) * iNewWidth + 4 * j];
pRaw[i * 3 * X + 3 * j + 1] = pBmp[54 + (Y - 1 - i) * iNewWidth + 4 * j + 1];
pRaw[i * 3 * X + 3 * j + 2] = pBmp[54 + (Y - 1 - i) * iNewWidth + 4 * j + 2];
}
}
}
else
{
return -2;
}
return 0;
}
/**
* @author mickey
* @category 读取文件数据到byte数组
* @param filepath - [in]文件路径
* @return byte数组
* */
public static byte[] ReadData(string fileName)
{
using (FileStream fs = new FileStream(fileName, FileMode.Open, FileAccess.Read))
{
try
{
byte[] buffer = new byte[fs.Length];
fs.Read(buffer, 0, (int)fs.Length);
return buffer;
}
catch (Exception ex)
{
return null;
}
}
}
/**
* @author mickey
* @category 图像裸数据-> BMP格式图像数据
* @param pBmp - [out]BMP格式图像数据
* iBMPLen - [out]BMP格式图像数据长度
* pRaw - [in]图像裸数据
* iImgX - [in]图像宽度
* iImgY - [in]图像高度
* iChannels - [in]图像通道数
* @return 0 - 成功
* 其他 - 失败
* */
public static int Raw2Bmp(byte[] pBmp, int[] iBMPLen, byte[] pRaw, int X, int Y, int iChannels)
{
int num;
int i, j;
byte[] head = new byte[1078];
byte[] temp = { 0x42, 0x4d, // file header
0x00, 0x00, 0x00, 0x00, // file size***
0x00, 0x00, // reserved
0x00, 0x00,// reserved
0x36, 0x4, 0x00, 0x00,// head byte***
0x28, 0x00, 0x00, 0x00,// struct size
0x00, 0x00, 0x00, 0x00,// map width***
0x00, 0x00, 0x00, 0x00,// map height***
0x01, 0x00,// must be 1
0x08, 0x00,// color count***
0x00, 0x00, 0x00, 0x00, // compression
0x00, 0x00, 0x00, 0x00,// data size***
0x00, 0x00, 0x00, 0x00, // dpix
0x00, 0x00, 0x00, 0x00, // dpiy
0x00, 0x00, 0x00, 0x00,// color used
0x00, 0x00, 0x00, 0x00,// color important
};
Array.Copy(temp, 0, head, 0, temp.Length);
int iNewWidth = (iChannels * X + 3) / 4 * 4;
int iHeadLen = 54;
if (iChannels == 1)
{
iHeadLen = 1078;
}
int iFileSize = iHeadLen + iNewWidth * Y;
num = iFileSize;
head[2] = (byte)(num & 0xFF);
num = num >> 8;
head[3] = (byte)(num & 0xFF);
num = num >> 8;
head[4] = (byte)(num & 0xFF);
num = num >> 8;
head[5] = (byte)(num & 0xFF);
num = iHeadLen;
head[10] = (byte)(num & 0xFF);
num = num >> 8;
head[11] = (byte)(num & 0xFF);
num = num >> 8;
head[12] = (byte)(num & 0xFF);
num = num >> 8;
head[13] = (byte)(num & 0xFF);
// 确定图象宽度数值
num = X;
head[18] = (byte)(num & 0xFF);
num = num >> 8;
head[19] = (byte)(num & 0xFF);
num = num >> 8;
head[20] = (byte)(num & 0xFF);
num = num >> 8;
head[21] = (byte)(num & 0xFF);
// 确定图象高度数值
num = Y;
head[22] = (byte)(num & 0xFF);
num = num >> 8;
head[23] = (byte)(num & 0xFF);
num = num >> 8;
head[24] = (byte)(num & 0xFF);
num = num >> 8;
head[25] = (byte)(num & 0xFF);
//BitCount
num = iChannels * 8;
head[28] = (byte)(num & 0xFF);
num = num >> 8;
head[29] = (byte)(num & 0xFF);
//Data size
num = Y * iNewWidth;
head[34] = (byte)(num & 0xFF);
num = num >> 8;
head[35] = (byte)(num & 0xFF);
num = num >> 8;
head[36] = (byte)(num & 0xFF);
num = num >> 8;
head[37] = (byte)(num & 0xFF);
// 确定调色板数值
j = 0;
for (i = 54; i < 1078; i = i + 4)
{
head[i] = head[i + 1] = head[i + 2] = (byte)j;
head[i + 3] = 0;
j++;
}
// 写入文件头
Array.Copy(head, 0, pBmp, 0, iHeadLen);
// 写入图象数据
for (i = 0; i < Y; i++)
{
Array.Copy(pRaw, i * iChannels * X, pBmp,
iHeadLen + (Y - 1 - i) * iNewWidth, iChannels * X);
}
//输出文件大小
iBMPLen[0] = iFileSize;
return 0;
}
/**
* @author mickey
* @category 保存数据为文件
* @param filepath - [in]文件路径
* buffer - [in]数据缓存
* size - [in]数据长度
* @return 0 - 成功
* 其他 - 失败
* */
public static int SaveData(string fileName, byte[] buffer, int size)
{
FileStream fs = new FileStream(fileName, FileMode.Create);
fs.Write(buffer, 0, size);
fs.Dispose();
return 0;
}
}
}
3. C#测试工程源代码
编译器版本:VS2015