zoukankan      html  css  js  c++  java
  • 【转】bmp文件格式详解

    先区分几个概念:16色和16位色一样吗?
    不一样!
    颜色位数,即是用多少位字节表示的值,每一位可以表示0和1两值。通常图片的颜色深度,简称色深,就是用位数来表示的,所以,我通常会看到8位色,16位色,24位色和32位色。
    而我们在其它地方看到的又是16色,256色,16777216色等等,这些怎么一回事呢?
    16色即代表16种颜色,256色即256种颜色,8位色就是用8个位来表示的颜色,即2的8次方,就是256色,16位色2的16次方,就是65536色,24位即16777216色,32位即4294967296色
    他 们之间并不存在转换关系,只有兼容关系,你不可能将红色转换为蓝色,也不可能将一个黑白的图片转换成一个彩色图片,但可以将彩色图片变成黑白图片,就像以 前的黑白电视机播放彩色录像始终是黑白的,而彩色电视机播放黑白的录像还是黑白的,当由多到少时,它会丢失颜色数据,当由少到多时,并不会还原丢失的数 据。
    好了,切入正题:
    bmp文件结构解析:
    一个bmp图片最多由4大部分组成:BITMAPFILEHEADER结构体,BITMAPINFOHEADER结构体,RGBQUAD结构体(这个结构体可以有,也可以没有),DIB数据区。其中DIB意思就是Device-Independent Bitmap(设备无关位图)。

    一个bmp文件以BITMAPFILEHEADER结构体开始,

    typedef struct tagBITMAPFILEHEADER {

    WORD bfType;//固定为0x4d42;
        DWORD bfSize; //文件大小
        WORD bfReserved1; //保留字,不考虑
        WORD bfReserved2; //保留字,同上
        DWORD bfOffBits; //实际位图数据的偏移字节数,即前三个部分长度之和
        } BITMAPFILEHEADER;

     BITMAPFILEHEADER的第1个属性是bfType(2字节),这里恒定等于0x4D42。由于内存中的数据排列高位在左,低位在右,所以内存中从左往右看就显示成(42 4D),所以在winhex中头两个 字节显示为(42 4D)就是这样形成的,以后的数据都是这个特点,不再作重复说明。

    BITMAPFILEHEADER的第2个属性是bfSize(4字节),表示整个bmp文件的大小。

    BITMAPFILEHEADER的第3个、第4个属性分别是bfReserved1、bfReserved2(各2字节),这里是2个保留属性,都为0,这里等于&H0000、0x0000。

    BITMAPFILEHEADER的第5个属性是bfOffBits(4字节),表示DIB数据区在bmp文件中的位置偏移量,比如等于0x00000076=118,表示数据区从文件开始往后数的118字节开始。

     BITMAPFILEHEADER结构体这里就讲完了,大家会发现BITMAPFILEHEADER只占了bmp文件开始的14字节长度,但需要 特别说明的是:我们在编程时,经常是以二进制的形式打开一个bmp文件,然后将其开头的14个字节读入自己定义的BITMAPFILEHEADER 结构体中,如果按上述定义结构体的方式,需要注意,这个自己定义的结构体在内存中由于字节对齐,会占用16字节的空间,因而直接读入16字节,按字节顺序 赋值给结构体,出来的数据是错误的,这样的话,可以先将这14字节的数据读入到一个缓冲器中,然后从缓冲器中按字节对结构体进行赋值。详细程序见后附录。 鉴于此中原因,在vb中定义一个BITMAPFILEHEADER结构体变量,其长度占了16个字节,原因就是第1个属性本来应该只分配2个字节,但实际被 分配了4个字节,多出来2个字节,所以如果想保存一张bmp图片,写入BITMAPFILEHEADER结构体时一定要注意这一点。

    接下来是BITMAPINFO结构体部分。BITMAPINFO段由两部分组成:BITMAPINFOHEADER结构体和RGBQUAD结构 体。其中RGBQUAD结构体表示图片的颜色信息,有些时候可以省略,一般的24位图片和32位图片都不带RGBQUAD结构体,因为DIB数据区直接表 示的RGB值,一般4位图片和8位图片才带有RGBQUAD结构体。(多少位的图片就是用多少位来表示一个颜色信息,例如4位图片表示用4个bit来表示 一个颜色信息。)一个bmp文件中有没有RGBQUAD结构体,可以根据前面BITMAPFILEHEADER结构体的第5个属性bfOffBits来判 断,因为BITMAPINFOHEADER结构体长度为40bit,如果BITMAPINFOHEADER结构体结束后还未到DIB数据区的偏移量,就说 明接下来的数据是RGBQUAD结构体部分。

    下面进入正题BITMAPINFOHEADER部分。

    typedef struct tagBITMAPINFOHEADER{
     //public:
     DWORD biSize; //指定此结构体的长度,为40
     LONG biWidth; //位图宽
     LONG biHeight; //位图高
     WORD biPlanes; //平面数,为1
     WORD biBitCount; //采用颜色位数,可以是1,2,4,8,16,24,新的可以是32
     DWORD biCompression; //压缩方式,可以是0,1,2,其中0表示不压缩
     DWORD biSizeImage; //实际位图数据占用的字节数
     LONG biXPelsPerMeter; //X方向分辨率
     LONG biYPelsPerMeter; //Y方向分辨率
     DWORD biClrUsed; //使用的颜色数,如果为0,则表示默认值(2^颜色位数)
     DWORD biClrImportant; //重要颜色数,如果为0,则表示所有颜色都是重要的
    } BITMAPINFOHEADER;

    BITMAPINFOHEADER的第1个属性是biSize(4字节),表示BITMAPINFOHEADER结构体的长度,最常见的长度是40字节。

    BITMAPINFOHEADER的第2个属性是biWidth(4字节),表示bmp图片的宽度

    BITMAPINFOHEADER的第3个属性是biHeight(4字节),表示bmp图片的高度

    BITMAPINFOHEADER的第4个属性是biPlanes(2字节),表示bmp图片的平面属,显然显示器只有一个平面,所以恒等于1,这里等于0x0001。

    BITMAPINFOHEADER的第5个属性是biBitCount(2字节),表示bmp图片的颜色位数,即1位图(单色或二值图像),8位图,16位图,24位图、32位图等等。

    BITMAPINFOHEADER的第6个属性是biCompression(4字节),表示图片的压缩属性,bmp图片是不压缩的,等于0,所以这里为0x00000000。

    BITMAPINFOHEADER的第7个属性是biSizeImage(4字节),表示bmp图片数据区的大小,当上一个数值biCompression等于0时,这里的值可以省略不填,所以这里等于0x00000000。

    BITMAPINFOHEADER的第8个属性是biXPelsPerMeter(4字节),表示图片X轴每米多少像素,可省略,这里等于0x00000EC3=3779像素/米。

    BITMAPINFOHEADER的第9个属性是biYPelsPerMeter(4字节),表示图片Y轴每米多少像素,可省略,这里等于0x00000EC3=3779像素/米。

    BITMAPINFOHEADER的第10个属性是biClrUsed(4字节),表示使用了多少个颜色索引表,一般biBitCount属性小于16才会用到,等于0时表示有2^biBitCount个颜色索引表,所以这里仍等于0x00000000。

    BITMAPINFOHEADER的第11个属性是biClrImportant(4字节),表示有多少个重要的颜色,等于0时表示所有颜色都很重要,所以这里等于0x00000000。

    至此BITMAPINFOHEADER结构体结束。

    由于这个图片到这里还未到达DIB数据区的偏移量,或者说由于BITMAPINFOHEADER的第5个属性是biBitCount<16,也就是在1位图(只有两种颜色),4位图(只有2^4=16种颜色),8位图(只有2^8=256种颜色)的情况下,此时会有颜色表,也就是接下来的部分:RGBQUAD结构体。

    //调色板Palette,当然,这里是对那些需要调色板的位图文件而言的。24位和32位是不需要调色板的。
    //(调色板结构体个数等于使用的颜色数,即是多少色图就有多少个,4位图16色,就有16个RGBQUAD结构体。)

    typedef struct tagRGBQUAD {
    //public:
    BYTE rgbBlue; //该颜色的蓝色分量
    BYTE rgbGreen; //该颜色的绿色分量
    BYTE rgbRed; //该颜色的红色分量
    BYTE rgbReserved; //保留值
    } RGBQUAD;

    这里举个4位图也就是16色图的例子:一 个RGBQUAD结构体只占用4字节空间,从左到右每个字节依次表示(蓝色,绿色,红色,未使用)。举例的这个图片我数了数总共有16个RGBQUAD结 构体,由于该图片是4位图,2^4正好等于16,所以它把16种颜色全部都枚举出来了,这些颜色就是一个颜色索引表。颜色索引表编号从0开始,总共16个 颜色,所以编号为0-15。从winhex中可以看到按照顺序,这16个RGBQUAD结构体依次为:

    编号:(蓝,绿,红,空)

    0号:(00,00,00,00)

    1号:(00,00,80,00)

    2号:(00,80,00,00)

    3号:(00,80,80,00)

    4号:(80,00,00,00)

    5号:(80,00,80,00)

    6号:(80,80,00,00)

    7号:(80,80,80,00)

    8号:(C0,C0,C0,00)

    9号:(00,00,FF,00)

    10号:(00,FF,00,00)

    11号:(00,FF,FF,00)

    12号:(FF,00,00,00)

    13号:(FF,00,FF,00)

    14号:(FF,FF,00,00)

    15号:(FF,FF,FF,00)

    到这里,正好满足DIB数据区的偏移量,所以后面的字节就是图片内容了。这里需要提醒的是所有的DIB数据扫描行是上下颠倒的,也就是说一幅图片先绘制底部的像素,再绘制顶部的像素,所以这些DIB数据所表示的像素点就是从图片的左下角开始,一直表示到图片的右上角。

     

    程序附录:

    // std.cpp : Defines the entry point for the console application.
    //

    #include "stdafx.h"


    //ReadBitMap
    //
    #include
    #include
    #include
    #include
    #include


    #define WIDTHBYTES(bits) (((bits)+31)/32*4)

    typedef unsigned char BYTE;
    typedef unsigned short WORD;
    typedef unsigned long DWORD;
    typedef long LONG;


    //位图文件头信息结构定义
    //其中不包含文件类型信息(由于结构体的内存结构决定,要是加了的话将不能正确读取文件信息)

    typedef struct tagBITMAPFILEHEADER {

     WORD bfType;//固定为0x4d42
     DWORD bfSize; //文件大小
     WORD bfReserved1; //保留字,不考虑
     WORD bfReserved2; //保留字,同上
     DWORD bfOffBits; //实际位图数据的偏移字节数,即前三个部分长度之和
    } BITMAPFILEHEADER;


    //信息头BITMAPINFOHEADER,也是一个结构,其定义如下:

    typedef struct tagBITMAPINFOHEADER{
     //public:
     DWORD biSize; //指定此结构体的长度,为40
     LONG biWidth; //位图宽
     LONG biHeight; //位图高
     WORD biPlanes; //平面数,为1
     WORD biBitCount; //采用颜色位数,可以是1,2,4,8,16,24,新的可以是32
     DWORD biCompression; //压缩方式,可以是0,1,2,其中0表示不压缩
     DWORD biSizeImage; //实际位图数据占用的字节数
     LONG biXPelsPerMeter; //X方向分辨率
     LONG biYPelsPerMeter; //Y方向分辨率
     DWORD biClrUsed; //使用的颜色数,如果为0,则表示默认值(2^颜色位数)
     DWORD biClrImportant; //重要颜色数,如果为0,则表示所有颜色都是重要的
    } BITMAPINFOHEADER;


    //调色板Palette,当然,这里是对那些需要调色板的位图文件而言的。24位和32位是不需要调色板的。
    //(似乎是调色板结构体个数等于使用的颜色数。)

    typedef struct tagRGBQUAD {
     //public:
     BYTE rgbBlue; //该颜色的蓝色分量
     BYTE rgbGreen; //该颜色的绿色分量
     BYTE rgbRed; //该颜色的红色分量
     BYTE rgbReserved; //保留值
    } RGBQUAD;

     

    void showBmpHead(BITMAPFILEHEADER* pBmpHead)
    {
     printf("位图文件头: ");
     printf("bmp格式标志bftype:0x%x ",pBmpHead->bfType );
     printf("文件大小:%d ",pBmpHead->bfSize);
     printf("保留字:%d ",pBmpHead->bfReserved1);
     printf("保留字:%d ",pBmpHead->bfReserved2);
     printf("实际位图数据的偏移字节数:%d ",pBmpHead->bfOffBits);

    }


    void showBmpInforHead(tagBITMAPINFOHEADER* pBmpInforHead)
    {
     printf("位图信息头: ");
     printf("结构体的长度:%d ",pBmpInforHead->biSize);
     printf("位图宽:%d ",pBmpInforHead->biWidth);
     printf("位图高:%d ",pBmpInforHead->biHeight);
     printf("biPlanes平面数:%d ",pBmpInforHead->biPlanes);
     printf("biBitCount采用颜色位数:%d ",pBmpInforHead->biBitCount);
     printf("压缩方式:%d ",pBmpInforHead->biCompression);
     printf("biSizeImage实际位图数据占用的字节数:%d ",pBmpInforHead->biSizeImage);
     printf("X方向分辨率:%d ",pBmpInforHead->biXPelsPerMeter);
     printf("Y方向分辨率:%d ",pBmpInforHead->biYPelsPerMeter);
     printf("使用的颜色数:%d ",pBmpInforHead->biClrUsed);
     printf("重要颜色数:%d ",pBmpInforHead->biClrImportant);
    }

    void showRgbQuan(tagRGBQUAD* pRGB)
    {
     printf("(%-3d,%-3d,%-3d) ",pRGB->rgbRed,pRGB->rgbGreen,pRGB->rgbBlue);

    }

     

    void main()
    {

     BITMAPFILEHEADER bitHead;
     BITMAPINFOHEADER bitInfoHead;
     FILE* pfile;

     char strFile[50];
     char *BmpFileHeader;
     WORD *temp_WORD;
     DWORD *temp_DWORD;
     printf("please input the .bmp file name: ");
     scanf("%s",strFile);
     
     pfile = fopen(strFile,"rb");//打开文件
        BmpFileHeader=(char *)calloc(14,sizeof(char));
     if(pfile!=NULL)
     {
      printf("file bkwood.bmp open success. ");
      //读取位图文件头信息
      
      
      
      fread(BmpFileHeader,1,14,pfile);
      temp_WORD=(WORD* )(BmpFileHeader);
      bitHead.bfType=*temp_WORD;
      if(bitHead.bfType != 0x4d42)
      {
       printf("file is not .bmp file!");
       
       return;
      }
      temp_DWORD=(DWORD *)(BmpFileHeader+sizeof(bitHead.bfType));
      bitHead.bfSize=*temp_DWORD;
      temp_WORD=(WORD*)(BmpFileHeader+sizeof(bitHead.bfType)+sizeof(bitHead.bfSize));
      bitHead.bfReserved1=*temp_WORD;
      temp_WORD=(WORD*)(BmpFileHeader+sizeof(bitHead.bfType)+sizeof(bitHead.bfSize)+sizeof(bitHead.bfReserved1));
      bitHead.bfReserved2=*temp_WORD;
      temp_DWORD=(DWORD*)(BmpFileHeader+sizeof(bitHead.bfType)+sizeof(bitHead.bfSize)+sizeof(bitHead.bfReserved1)+sizeof(bitHead.bfReserved2));
      bitHead.bfOffBits=*temp_DWORD;
     
      
      
      showBmpHead(&bitHead);
      printf(" ");
     
      //读取位图信息头信息
      fread(&bitInfoHead,1,sizeof(BITMAPINFOHEADER),pfile);
      showBmpInforHead(&bitInfoHead);
      printf(" ");
      
     }
     else
     {
      printf("file open fail! ");
      return;
     }
     
     tagRGBQUAD *pRgb ;

     if(bitInfoHead.biBitCount < 24)//有调色板
     {
      //读取调色盘结信息
      long nPlantNum = long(pow(2,double(bitInfoHead.biBitCount))); // Mix color Plant Number;
      pRgb=(tagRGBQUAD *)malloc(nPlantNum*sizeof(tagRGBQUAD));
      memset(pRgb,0,nPlantNum*sizeof(tagRGBQUAD));
      int num = fread(pRgb,4,nPlantNum,pfile);

      printf("Color Plate Number: %d ",nPlantNum);

      printf("颜色板信息: ");
      for (int i =0; i  {
       if (i%5==0)
       {
        printf(" ");
       }
       showRgbQuan(&pRgb[i]);

      }

      printf(" ");

     }


     int width = bitInfoHead.biWidth;
     int height = bitInfoHead.biHeight;
     //分配内存空间把源图存入内存
     int l_width = WIDTHBYTES(width* bitInfoHead.biBitCount);//计算位图的实际宽度并确保它为32的倍数
     BYTE *pColorData=(BYTE *)malloc(height*l_width);
     memset(pColorData,0,height*l_width);
     long nData = height*l_width;

     //把位图数据信息读到数组里
     fread(pColorData,1,nData,pfile);

     

     //将位图数据转化为RGB数据
     tagRGBQUAD* dataOfBmp;
     dataOfBmp = (tagRGBQUAD *)malloc(width*height*sizeof(tagRGBQUAD));//用于保存各像素对应的RGB数据
     memset(dataOfBmp,0,width*height*sizeof(tagRGBQUAD));

     if(bitInfoHead.biBitCount<24)//有调色板,即位图为非真彩色
     {
      int k;
      int index = 0;
      if (bitInfoHead.biBitCount == 1)
      {
       for(int i=0;i    for(int j=0;j    {
         BYTE mixIndex= 0;
         k = i*l_width + j/8;//k:取得该像素颜色数据在实际数据数组中的序号
         //j:提取当前像素的颜色的具体值
         mixIndex = pColorData[k];
         switch(j%8)
         {
         case 0:
          mixIndex = mixIndex<<7;
          mixIndex = mixIndex>>7;
          break;
         case 1:
          mixIndex = mixIndex<<6;
          mixIndex = mixIndex>>7;
          break;
         case 2:
          mixIndex = mixIndex<<5;
          mixIndex = mixIndex>>7;
          break;

         case 3:
          mixIndex = mixIndex<<4;
          mixIndex = mixIndex>>7;
          break;
         case 4:
          mixIndex = mixIndex<<3;
          mixIndex = mixIndex>>7;
          break;

         case 5:
          mixIndex = mixIndex<<2;
          mixIndex = mixIndex>>7;
          break;
         case 6:
          mixIndex = mixIndex<<1;
          mixIndex = mixIndex>>7;
          break;

         case 7:
          mixIndex = mixIndex>>7;
          break;
         }

         //将像素数据保存到数组中对应的位置
         dataOfBmp[index].rgbRed = pRgb[mixIndex].rgbRed;
         dataOfBmp[index].rgbGreen = pRgb[mixIndex].rgbGreen;
         dataOfBmp[index].rgbBlue = pRgb[mixIndex].rgbBlue;
         dataOfBmp[index].rgbReserved = pRgb[mixIndex].rgbReserved;
         index++;

        }
      }

      if(bitInfoHead.biBitCount==2)
      {
       for(int i=0;i    for(int j=0;j    {
         BYTE mixIndex= 0;
         k = i*l_width + j/4;//k:取得该像素颜色数据在实际数据数组中的序号
         //j:提取当前像素的颜色的具体值
         mixIndex = pColorData[k];
         switch(j%4)
         {
         case 0:
          mixIndex = mixIndex<<6;
          mixIndex = mixIndex>>6;
          break;
         case 1:
          mixIndex = mixIndex<<4;
          mixIndex = mixIndex>>6;
          break;
         case 2:
          mixIndex = mixIndex<<2;
          mixIndex = mixIndex>>6;
          break;
         case 3:
          mixIndex = mixIndex>>6;
          break;
         }

         //将像素数据保存到数组中对应的位置
         dataOfBmp[index].rgbRed = pRgb[mixIndex].rgbRed;
         dataOfBmp[index].rgbGreen = pRgb[mixIndex].rgbGreen;
         dataOfBmp[index].rgbBlue = pRgb[mixIndex].rgbBlue;
         dataOfBmp[index].rgbReserved = pRgb[mixIndex].rgbReserved;
         index++;


        }
      }
      if(bitInfoHead.biBitCount == 4)
      {
       for(int i=0;i    for(int j=0;j    {
         BYTE mixIndex= 0;
         k = i*l_width + j/2;
         mixIndex = pColorData[k];
         if(j%2==0)
         {//低
          mixIndex = mixIndex<<4;
          mixIndex = mixIndex>>4;
         }
         else
         {//高
          mixIndex = mixIndex>>4;
         }

         dataOfBmp[index].rgbRed = pRgb[mixIndex].rgbRed;
         dataOfBmp[index].rgbGreen = pRgb[mixIndex].rgbGreen;
         dataOfBmp[index].rgbBlue = pRgb[mixIndex].rgbBlue;
         dataOfBmp[index].rgbReserved = pRgb[mixIndex].rgbReserved;
         index++;

        }

      }
      if(bitInfoHead.biBitCount == 8)
      {
       for(int i=0;i    for(int j=0;j    {
         BYTE mixIndex= 0;

         k = i*l_width + j;

         mixIndex = pColorData[k];

         dataOfBmp[index].rgbRed = pRgb[mixIndex].rgbRed;
         dataOfBmp[index].rgbGreen = pRgb[mixIndex].rgbGreen;
         dataOfBmp[index].rgbBlue = pRgb[mixIndex].rgbBlue;
         dataOfBmp[index].rgbReserved = pRgb[mixIndex].rgbReserved;
         index++;

     

        }
      }
      if(bitInfoHead.biBitCount == 16)
      {
       for(int i=0;i    for(int j=0;j    {
         WORD mixIndex= 0;

         k = i*l_width + j*2;
         WORD shortTemp;
         shortTemp = pColorData[k+1];
         shortTemp = shortTemp<<8;

         mixIndex = pColorData[k] + shortTemp;

         dataOfBmp[index].rgbRed = pRgb[mixIndex].rgbRed;
         dataOfBmp[index].rgbGreen = pRgb[mixIndex].rgbGreen;
         dataOfBmp[index].rgbBlue = pRgb[mixIndex].rgbBlue;
         dataOfBmp[index].rgbReserved = pRgb[mixIndex].rgbReserved;
         index++;
        }
      }
     }
     else//位图为24位真彩色
     {
      int k;
      int index = 0;
      for(int i=0;i   for(int j=0;j   {
        k = i*l_width + j*3;
        dataOfBmp[index].rgbRed = pColorData[k+2];
        dataOfBmp[index].rgbGreen = pColorData[k+1];
        dataOfBmp[index].rgbBlue = pColorData[k];
        index++;
       }
     }


     printf("像素数据信息: ");
    /*
     for (int i=0; i {
      if (i%5==0)
      {
       printf(" ");
      }
      showRgbQuan(&dataOfBmp[i]);
     }
    */
     fclose(pfile);
     
     if (bitInfoHead.biBitCount<24)
     {
      free(pRgb);
     }

     free(dataOfBmp);
     free(pColorData);
       free(BmpFileHeader);
     printf(" ");

  • 相关阅读:
    插入排序
    JavaMail学习笔记
    汉诺塔问题
    使用Three.js绘制一个虚拟城市
    jquery flotcharts使用简介
    用CSS hack技术解决浏览器兼容性问题.
    IE条件注释详解.
    让IE6也认识!important
    代码重构(转)
    模版+数据分离渲染方式的设计与实现
  • 原文地址:https://www.cnblogs.com/reddusty/p/4850408.html
Copyright © 2011-2022 走看看