参考:
/*
    位图文件(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
*/