参考:
/*
位图文件(bmp)
*/
#include <stdio.h>
#include <stdlib.h>
#pragma pack (1)
//自定义1字节对齐
typedef struct _BITMAPFILEHEADER_
//14 bytes 位图文件头
{
unsigned short int Type;
//BM(19778):Windows,BA、CI、CP、IC、PT:OS/2
unsigned int Size;
//文件的总总字节数
unsigned short int Reserved1;
//为0
unsigned short int Reserved2;
//为0
unsigned int OffBits;
//图片信息到文件开始的偏移量,即文件前三项的总字节数
} BITMAPFILEHEADER, *PBITMAPFILEHEADER;
typedef struct _BITMAPINFOHEADER_
//40 bytes 位图信息头
{
unsigned int Size;
//信息头的长度,为40
int Width;
//图像宽
int Height;
//图像高,>0倒向(显示时从下至上),<0正向
unsigned short int Planes;
//颜色平面数,常设为1
unsigned short int BitCount;
//每个像素占的位数,为1、4、8、16、24或32
//压缩类型,常设为0。
//
0:BI_RGB 不压缩
//
1:BI_RLE8 8bit编码,只用于8位位图
//
2:BI_RLE4 4bit编码,只用于4位位图
//
3:BI_BITFIELDS,用于16/32位位图
//
4:BI_JPEG JPEG位图含JPEG图像,仅用于打印机
//
5:BI_PNG PNG位图含PNG图像,仅用于打印机
unsigned int Compression;
unsigned int SizeImage;
//图像信息总字节数
int XPelsPerMeter;
//有符号整数,水平分辩率,用“像素/米”表示
int YPelsPerMeter;
//有符号整数,垂直分辩率,用“像素/米”表示
unsigned int ClrUsed;
//实际使用的颜色表中的颜色索引数,为0则使用所有调色板项
unsigned int ClrImportant;
//对图像显示有重要影响的颜色索引的数目,为0表示都重要。
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;
typedef struct _RGBLIST_
//颜色表
{
//颜色表中RGBQUAD结构数据的个数由BITMAPINFOHEADER的BitCount来确定:
//
当 BitCount = 1、4、8时,分别是2、16、256个表项
//
当 BitCount > 8 时,没有颜色表项。
unsigned char Blue;
//蓝色
unsigned char Green;
//绿色
unsigned char Red;
//红色
unsigned char Alpha;
//透明,不透明为0
} RGBLIST, *PRGLIST;
typedef struct _BITMAPINFO_
{
BITMAPINFOHEADER IHeader;
//位图信息头
RGBLIST RGBList[];
//颜色表,柔性数组。
} BITMAPINFO, *PBITMAPINFO;
typedef struct _RGB1bit_
{
unsigned char index0:1;
unsigned char index1:1;
unsigned char index2:1;
unsigned char index3:1;
unsigned char index4:1;
unsigned char index5:1;
unsigned char index6:1;
unsigned char index7:1;
} RGB1bit, *PRGB1bit;
typedef struct _RGB4bit_
{
unsigned char index0:4;
unsigned char index1:4;
} RGB4bit, *PRGB4bit;
typedef struct _RGB8bit_
{
unsigned char index;
} RGB8bit, *PRGB8bit;
typedef struct _RGB16bit_
{
union _RGB_
{
struct _RGB555_
{
unsigned short int N:1;
unsigned short int B:5;
unsigned short int G:5;
unsigned short int R:5;
} RGB555;
struct _RGB565_
{
unsigned short int B:5;
unsigned short int G:6;
unsigned short int R:5;
} RGB565;
} RGB;
} RGB16bit, *PRGB16bit;
typedef struct _RGB24bit_
{
unsigned char B;
unsigned char G;
unsigned char R;
} RGB24bit, *PRGB24bit;
typedef struct _RGB32bit_
{
unsigned char B;
unsigned char G;
unsigned char R;
unsigned char A;
} RGB32bit, *PRGB32bit;
#pragma pack () //取消自定义字节对齐
void _SetRGB(char *px, int BitCount)
{
PRGB1bit p1bit=NULL;
PRGB4bit p4bit=NULL;
PRGB8bit p8bit=NULL;
PRGB16bit p16bit=NULL;
PRGB24bit p24bit=NULL;
PRGB32bit p32bit=NULL;
switch(BitCount)
{
case 32:
p32bit = (PRGB32bit)px;
p32bit->B = 0;
p32bit->G = 0;
p32bit->R = 0xff;
//修改为红色,画1:1(45度角)线
p32bit->A = 0xff;
//透明,需系统支持。
break;
case 24:
p24bit = (PRGB24bit)px;
p24bit->B = 0;
p24bit->G = 0;
p24bit->R = 0xff;
//修改为红色,画1:1(45度角)线
break;
case 16:
p16bit = (PRGB16bit)px;
p16bit->RGB.RGB555.B = 0;
p16bit->RGB.RGB555.G = 0;
p16bit->RGB.RGB555.R = 0b11111;
//修改为红色,画1:1(45度角)线
break;
case 8:
p8bit = (PRGB8bit)px;
p8bit->index = 249;
//修改为红色(颜色表249号RGB色),画1:1(45度角)线
break;
case 4:
p4bit = (PRGB4bit)px;
p4bit->index0 = 0;
//修改为黑色(颜色表0号RGB色),画1:2线
p4bit->index1 = 0;
break;
case 1:
p1bit = (PRGB1bit)px;
p1bit->index0 = 1;
//修改为黑色(颜色表1号RGB色),画1:8线
p1bit->index1 = 1;
p1bit->index2 = 1;
p1bit->index3 = 1;
p1bit->index4 = 1;
p1bit->index5 = 1;
p1bit->index6 = 1;
p1bit->index7 = 1;
break;
default :
printf("\n暂不支持%dbit格式\n", BitCount);
break;
}
}
void _rgbdata(FILE *fp, char *buf, int BitCount, int Width, int Height)
{
int XBytes=1;
if (BitCount > 8)
XBytes = BitCount / 8;
//BitCount:1、4、8时1byte,16时2byte,24时3byte,32时4byte,
int RowBytes = (((Width * BitCount) + 31) >> 5) << 2;
//每行的字节数
int SkipBytes = 4 - ((Width * BitCount) >> 3) & 3;
//每行字节对齐而添加的字节数
char *px=NULL, *py=buf;
int x, y;
for (y=0; y<Height; y++,py+=RowBytes)
{
px = py;
for (x=0; x<(RowBytes-SkipBytes)/XBytes; x++,px+=XBytes)
{
if (x == y)
_SetRGB(px, BitCount);
fwrite(px, XBytes, 1, fp);
}
for (x=0; x<SkipBytes; x++) //行字节对齐
fwrite("\0", 1, 1, fp);
}
fwrite("\0\0", 2, 1, fp);
//文件尾2字节为0
}
PRGB24bit _Get24bitRGB(char *buf, int RowBytes, int x, int y)
{
PRGB24bit pRGB = (PRGB24bit)(buf + y*RowBytes + x*sizeof(RGB24bit));
printf("(%3d,%3d)\t%02x\t%02x\t%02x\n", x, y, pRGB->R, pRGB->G, pRGB->B);
return pRGB;
//返回x,y像点RGB
}
PRGB32bit _Get32bitRGB(char *buf, int RowBytes, int x, int y)
{
PRGB32bit pRGB = (PRGB32bit)(buf + y*RowBytes + x*sizeof(RGB32bit));
printf("(%3d,%3d)\t%02x\t%02x\t%02x\t%02x\n", x, y, pRGB->A, pRGB->R, pRGB->G, pRGB->B);
return pRGB;
//返回x,y像点RGB
}
main()
{
//char *bmpFile="test1bit.bmp";
//char *bmpFile="test4bit.bmp";
char *bmpFile="test8bit.bmp";
//char *bmpFile="test16bit.bmp";
//char *bmpFile="test24bit.bmp";
//char *bmpFile="test24bit正向.bmp";
//char *bmpFile="test32bit.bmp";
FILE *fp;
if ((fp=fopen(bmpFile,"rb")) == NULL)
return;
//文件头部分
BITMAPFILEHEADER stBFH;
fread(&stBFH, sizeof(BITMAPFILEHEADER), 1, fp);
printf("BITMAPFILEHEADER\n");
printf("
Type: %hu\n",
stBFH.Type);
printf("
Size: %u\n",
stBFH.Size);
printf("
Reserved1: %hu\n",
stBFH.Reserved1);
printf("
Reserved2: %hu\n",
stBFH.Reserved2);
printf("
OffBits: %u\n\n", stBFH.OffBits);
//图像信息部分
//颜色表大小 = 图像数据到文件开始的偏移量 - 文件头大小 - 信息头大小
//颜色表项目数 = 颜色表大小/4 = (图像数据到文件开始的偏移量 - 文件头大小 - 信息头大小) / 4
int RGBListBytes = stBFH.OffBits - sizeof(BITMAPFILEHEADER) - sizeof(BITMAPINFOHEADER);
PBITMAPINFO pBI = (PBITMAPINFO)malloc(sizeof(BITMAPINFO) + RGBListBytes);
fread(pBI, sizeof(BITMAPINFO)+RGBListBytes, 1, fp);
printf("BITMAPINFOHEADER\n");
printf("
Size: %u\n",
pBI->IHeader.Size);
printf("
Width: %d\n",
pBI->IHeader.Width);
printf("
Height: %d\n",
pBI->IHeader.Height);
printf("
Planes: %hu\n",
pBI->IHeader.Planes);
printf("
BitCount: %hu\n",
pBI->IHeader.BitCount);
printf("
Compression: %u\n",
pBI->);
printf("
SizeImage: %u\n",
pBI->IHeader.SizeImage);
printf("
XPelsPerMeter: %d\n",
pBI->IHeader.XPelsPerMeter);
printf("
YPelsPerMeter: %d\n",
pBI->IHeader.YPelsPerMeter);
printf("
ClrUsed: %u\n",
pBI->IHeader.ClrUsed);
printf("
ClrImportant: %u\n\n", pBI->IHeader.ClrImportant);
if (RGBListBytes > 0) //有颜色表,列颜色表
{
int i;
printf("Colors List\n");
printf("Index\tAlpha\tRed\tGreen\tBlue\n");
for (i=0; i<RGBListBytes/4; i++)
printf("%3d\t%02x\t%02x\t%02x\t%02x\n", i, pBI->RGBList[i].Alpha, pBI->RGBList[i].Red, pBI->RGBList[i].Green, pBI->RGBList[i].Blue);
}
//图像数据信息
int bmpHeight = pBI->IHeader.Height;
if (bmpHeight < 0)
//Height<0图像数据按行从上至下(正向)
bmpHeight = ~bmpHeight + 1; //取绝对值
int RowBytes = (((pBI->IHeader.Width * pBI->IHeader.BitCount) + 31) >> 5) << 2; //每行的字节数
int DataSize = RowBytes * bmpHeight;
//位图数据区的大小
int SkipBytes = 4 - ((pBI->IHeader.Width * pBI->IHeader.BitCount) >> 3) & 3;
//每行字节对齐而添加的字节数
printf("\nBitmap Data Info\n");
printf("
RowBytes: %d\n", RowBytes);
printf("
DataSize: %d\n", DataSize);
printf("
SkipBytes: %d\n\n", SkipBytes);
//图像数据部分
char *buf = (char *)malloc(pBI->IHeader.SizeImage);
fread(buf, pBI->IHeader.SizeImage, 1, fp);
fclose(fp);
//另存为 test__.bmp
fp = fopen("test__.bmp","wb");
fwrite(&stBFH, sizeof(BITMAPFILEHEADER), 1, fp);
//文件头部分
fwrite(pBI, sizeof(BITMAPINFO)+RGBListBytes, 1, fp);
//图像信息部分
_rgbdata(fp, buf, pBI->IHeader.BitCount, pBI->IHeader.Width, bmpHeight);//图像数据部分
if (pBI->IHeader.BitCount == 24)
{
printf("(x,y)\t\tR\tG\tB\n");
_Get24bitRGB(buf, RowBytes,
0,
0);
_Get24bitRGB(buf, RowBytes, 123, 124);
_Get24bitRGB(buf, RowBytes, 123, 123);
_Get24bitRGB(buf, RowBytes, 122, 123);
}
else if (pBI->IHeader.BitCount == 32)
{
printf("(x,y)\t\tA\tR\tG\tB\n");
_Get32bitRGB(buf, RowBytes,
0,
0);
_Get32bitRGB(buf, RowBytes, 123, 124);
_Get32bitRGB(buf, RowBytes, 123, 123);
_Get32bitRGB(buf, RowBytes, 122, 123);
}
fclose(fp);
free(pBI);
free(buf);
}
/*
位图数据
位图数据记录了位图的每一个像素值,记录顺序是在扫描行内是从左到右,
扫描行之间由 BITMAPINFOHEADER 的 Height 决定:
Height > 0 时倒向(从下至上)
Height < 0 时正向(从上至下)
位图的一个像素值所占的字节数由 BITMAPINFOHEADER 的 BitCount 决定:
当 BitCount =
1 时,8个像素颜色表索引号占1个字节
当 BitCount =
4 时,2个像素颜色表索引号占1个字节
当 BitCount =
8 时,1个像素颜色表索引号占1个字节
当 BitCount = 16 时,1个像素占2个字节颜色值,按顺序分别为N(1位),B(5位),G(5位),R(5位)或 B(5位),G(6位),R(5位)
当 BitCount = 24 时,1个像素占3个字节颜色值,按顺序分别为B,G,R
当 BitCount = 32 时,1个像素占4个字节颜色值,按顺序分别为B,G,R,A(透明)
BitCount=16,16位色彩下,每个像素占2个字节,这16位,分成三段分别表示像素的R、G、B值。
现在的显示卡有两种:555格式的RGB分别各占5位最高位空、565格式G的值占6位,其他两个分量各占5位。
NRRRRRGGGGGBBBBB(555)
RRRRRGGGGGGBBBBB(565)
16位图形的格式, 如果使用非压缩格式, 一般是555最高位空, 否则就是565, 需要获得三个RGB掩码
然后同565进行与操作得到需要的RGB, 这个自然需要进行一些移位操作。
BitCount=16 表示位图最多有216种颜色,每个色素用16位(2个字节)表示,这种格式叫作高彩色,
或叫增强型16位色,或64K色,它的情况比较复杂,当Compression成员的值是BI_RGB时,它没有调色板。
16位中,最低的5位表示蓝色分量,中间的5位表示绿色分量,高的5位表示红色分量,一共占用了15位,
最高的一位保留,设为0。这种格式也被称作555 16位位图。如果Compression成员的值是BI_BITFIELDS,
那么情况就复杂了,首先是原来调色板的位置被三个DWORD变量占据,称为红、绿、蓝掩码。分别用于描
述红、绿、蓝分量在16位中所占的位置。
在Windows 95(或98)中,系统可接受两种格式的位域:555和565,在555格式下,红、绿、蓝的掩码
分别是:0x7C00、0x03E0、0x001F,而在565格式下,它们则分别为:0xF800、0x07E0、0x001F。
你在读取一个像素之后,可以分别用掩码“与”上像素值,从而提取出想要的颜色分量(当然还要再经过适当的左右移操作)。
在NT系统中,则没有格式限制,只不过要求掩码之间不能有重叠。(注:这种格式的图像使用起来是比较麻烦的,
不过因为它的显示效果接近于真彩,而图像数据又比真彩图像小的多,所以,它更多的被用于游戏软件)。
RGB888 = R[8bit] + G[8bit] + B[8bit]
RGB565 = R[5bit] + G[6bit] + B[5bit]
R[8bit] & 0b00011111 = R[5bit]
G[8bit] & 0b00111111 = G[6bit]
B[8bit] & 0b00011111 = B[5bit]
Windows规定一个扫描行所占的字节数必须是
4的倍数(即以long为单位),不足的以0填充,
biSizeImage = ((((bi.biWidth * bi.biBitCount) + 31) & ~31) / 8) * bi.biHeight;
=========================================================
对齐规则
Windows默认的扫描的最小单位是4字节,如果数据对齐满足这个值的话对于数据的获取速度
等都是有很大的增益的。因此,BMP图像顺应了这个要求,要求每行的数据的长度必须是4的
倍数,如果不够需要进行比特填充(以0填充),这样可以达到按行的快速存取。这时,位图
数据区的大小就未必是 图片宽×每像素字节数×图片高 能表示的了,因为每行可能还需要
进行比特填充。
每行的字节数为:
int iLineByteCnt = (((m_iImageWidth * m_iBitsPerPixel) + 31) >> 5) << 2;
位图数据区的大小为:
m_iImageDataSize = iLineByteCnt * m_iImageHeight;
在扫描完一行数据后,也可能接下来的数据并不是下一行的数据,可能需要跳过一段填充数据:
skip = 4 - ((m_iImageWidth * m_iBitsPerPixel)>>3) & 3;
=========================================================
需要注意的是:
我们讲的主要是PC机上的位图文件的构成,对于嵌入式平台,可能在调色板数据段与PC机的不同。
如在嵌入式平台上常见的16位r5g6b5位图实际上采用的掩模的方式而不是索引的方式来表示图像。
此时,在调色板数据段共有四个部分,每个部分为四个字节,实际表示的是彩色版规范。
即:
第一个部分是红色分量的掩模
第二个部分是绿色分量的掩模
第三个部分是蓝色分量的掩模
第四个部分是Alpha分量的掩模(缺省为0)
典型的调色板规范在文件中的顺序为为:
00F8 0000 E007 0000 1F00 0000 0000 0000
其中:
00F8 0000 为 FB00h = 1111100000000000(二进制),是蓝红分量的掩码。
E007 0000 为 07E0h = 0000011111100000(二进制),是绿色分量的掩码。
1F00 0000 为 001Fh = 0000000000011111(二进制),是蓝色分量的掩码。
0000 0000 设置为0。
将掩码跟像素值进行“与”运算再进行移位操作就可以得到各色分量值。
看看掩码,就可以明白事实上在每个像素值的两个字节16位中,按从高到低取5、6、5位
分别就是r、g、b分量值。取出分量值后把r、g、b值分别乘以8、4、8就可以补齐每个分
量为一个字节,再把这三个字节按BGR组合,放入存储器,就可以转换为24位标准BMP格式了。
这样我们假设在位图数据区有一个像素的数据在文件中表示为02 F1。
这个数据实际上应为F102:
r = (F102 AND F800) >> 8 = F0h = 240
g = (F102 AND 07E0)>> 3 = 20h = 32
b = (F102 AND 001F) << 3 = 10h =16
*/