一、文件格式
 Bmp文件是非常常用的位图文件,无论是游戏还是其他都被广泛使用。针对bmp文件的处理也有一堆现成的api进行调用,然而文件内部究竟怎样,如何自己来解析这样的文件呢?为了消除无聊,我用了几天时间来研究了一下,同时作为学习笔记,进行记录。
 首先,整个bmp文件的内容可以分为3到4块。之所以分为3到4块而不是固定的值,是因为,对于bmp来说可能存在调色板或者一些掩码。具体稍候讨论。
 第一块是bmp的文件头用于描述整个bmp文件的情况。结构如下:
typedef struct tagBITMAPFILEHEADER {
  WORD    bfType;   
  DWORD   bfSize;
  WORD    bfReserved1;
  WORD    bfReserved2;
  DWORD   bfOffBits;
 } BITMAPFILEHEADER, *PBITMAPFILEHEADER;
 这些信息相当有用,如果你想直接来解析bmp文件。第一个bfType用于表示文件类型,如果它是bmp文件,那么它这个位置的值一定是”BM” 也就是0x4D42。第二个bfSize表示整个文件的字节数。第三第四个 则保留,目前无意义,最后一个相当重要,表示,位图的数据信息离文件头的偏移量,以字节为单位。
 第二块是位图信息头,即BITMAPINFOHEADER,用于描述整个位图文件的情况。以下挑重要的数据进行解释
 typedef struct tagBITMAPINFOHEADER{
   DWORD  biSize; //表示本结构的大小
   LONG   biWidth; //位图的宽度
   LONG   biHeight; //位图的高度
WORD   biPlanes; //永远为1 ,由于没有用过所以 没做研究 附msdn解释
 //Specifies the number of planes for the target device. This value must be set to 1.
   WORD   biBitCount;//位图的位数  分为1 4 8 16 24 32 本文没对1 4 进行研究
   DWORD  biCompression; //本以为压缩类型,但是却另外有作用,稍候解释
   DWORD  biSizeImage; //表示位图数据区域的大小以字节为单位
   LONG   biXPelsPerMeter;
   LONG   biYPelsPerMeter;
   DWORD  biClrUsed;
   DWORD  biClrImportant;
 } BITMAPINFOHEADER, *PBITMAPINFOHEADER;
 第三块就是调色板信息或者掩码部分,如果是8位位图 则存放调色板 ;16 与32位 位图则存放RGB颜色的掩码,这些掩码以DWORD大小来存放。
 最后一块就是位图的数据实体。
 以上文件信息可以在任意一篇bmp文件结构的文章中找到描述,所以本文只是稍微带过。
二、4字节对其问题
 关于数据读取。Bmp文件有个重要特性,那就是对于数据区域而言,每行的数据它必须凑满4字节,如果没有满,则用冗余的数据来补齐。这个特性直接影响到我们读取位图数据的方法,因为在我们看来 (x,y)的数据应该在 y*width+x这样的位置上 但是因为会有冗余信息 那么必须将width用width+该行的冗余量来处理,而由于位图文件有不同的位数,所以这样的计算也不尽相同。
 下面列出计算偏移量的一般公式。
 首先将位图信息读入一个UCHAR 的buffer中 :
 8位:
 int pitch;
  if(width%4==0){
   pitch=width;
  }else{
   pitch=width+4-width%4;
  }
  index=buffer[y*pitch+x]; 因为8位位图的数据区域存放的是调色板索引值,所以只需读取这个index
 16位
  int pitch=width+width%2;
  buffer[(y*pitch+x)*2]
 buffer[(i*pitch+j)*2+1]
 两个UCHAR内,存放的是(x,y)处的颜色信息
   24位
  int pitch=width%4;
  buffer[(y*width+x)*3+y*pitch];
  buffer[(y*width+x)*3+y*pitch+1];
 buffer[(y*width+x)*3+y*pitch+2];
   32位
  由于一个象素就是4字节 所以无需补齐

虽然计算比较繁琐,但是这些计算是必须的,否则当你的位图每行的象素数不是4的倍数,那么y*width+x带给你的是一个扭曲的图片,当然如果你想做这样的旋转,也不错啊,至少我因为一开始没有考虑(不知道这个特性) 让一个每行象素少1字节的16位图片变成了扭曲的菱形。

三、有了数据分离RGB分量。
 由于我的测试代码用了GDI,所以我必须讲得到的某一个点的值 分离成 24位模式下的RGB分离,这不是一件容易的工作。位图麻烦的地方之一就是他的格式太多,所以我们还是要分格式再讨论。
 8位
 通过第二部分提到的操作我们得到了一个index,这个值的范围是0~255 一共256个 正好是调色板的颜色数量。
 在8位bmp图片中 数据信息前256个RGBQUAD的大小开始就是调色板的信息。不过如果要组织成调色板还要一定的转换 因为里面是RGBQUAD信息 r b 两个与调色板中的顺序是颠倒的。因为我不需要调色板设置所以我字节读取到RGBQUAD数组中,并且通过下面的表达式获取RGB值:
 UCHAR r=quad[index].rgbRed;
   UCHAR g=quad[index].rgbGreen;
   UCHAR b=quad[index].rgbBlue;
 16位
 这是最麻烦的一个。因为在处理时有555 565 两种格式的区别,而且还有所谓压缩类型的区别。
 之前的bitmapinfoheader里面提到一个biCompression
 现在我们分两种情况讨论:BI_RGB和BI_BITFIELDS
 当他等于BI_RGB时 只有555 这种格式,所以可以放心大胆的进行如下的数据分离:
 UCHAR b=buffer[(i*pitch+j)*2]&0x1F;
 UCHAR g=(((buffer[(i*pitch+j)*2+1]<<6)&0xFF)>>3)+(buffer[(i*pitch+j)*2]>>5);
 UCHAR r=(buffer[(i*pitch+j)*2+1]<<1)>>3;
 
 希望不要被这个表达式折磨的眼花缭乱,我想既然你在看这篇文章,你就有能力阅读这样的代码,否则只能说你还没有到阅读这方面的地步,需要去学习基础的语法了。
 有一点值得提醒的是由于有较多的位操作 ,所以在处理的时候在前一次操作的上面加上一对括号,我就曾经因为没有加而导致出现误差,另外虽然buffer中一个元素代表的是一个UCHAR 但是右移操作会自动增长为两字节 所以需要在进行一次与操作截取低位的1字节数据。
 现在讨论BI_BITFIELDS。
 这个模式下 既可以有555 也可以有565 。
 555 格式 xrrrrrgggggbbbbb
 565 格式 rrrrrggggggbbbbb
显然不同的格式处理不同,所以我们要首先判断处到底属于那种格式。
 Bitmapinfoheader的biCompression为BI_BITFIELDS时,在位图数据区域前存在一个RGB掩码的描述 是3个DWORD值,我们只需要读取其中的R或者G的掩码,来判断是那种格式。
 以红色掩码为例 0111110000000000的时候就是555格式 1111100000000000就是565格式。
 以下是565格式时的数据分离:
UCHAR b=buffer[(i*pitch+j)*2]&0x1F;
UCHAR g=(((buffer[(i*pitch+j)*2+1]<<5)&0xFF)>>2)+(buffer[(i*pitch+j)*2]>>5);
UCHAR r=buffer[(i*pitch+j)*2+1]>>3;

现在我们得到了RGB各自的分量,但是还有一个新的问题,那就是由于两字节表示了3个颜色  555下每个颜色最多到0x1F 565格式下最大的绿色分量也就0x3F。所以我们需要一个转换 color=color*255/最大颜色数 即可
 如565下RGB(r*0xFF/0x1F,g*0xFF/0x3F,b*0xFF/0x1F)
 24位
 UCHAR b=buffer[(i*width+j)*3+realPitch];
 UCHAR g=buffer[(i*width+j)*3+1+realPitch];
 UCHAR r=buffer[(i*width+j)*3+2+realPitch];
 32位
 UCHAR b=buffer[(i*width+j)*4];
 UCHAR g=buffer[(i*width+j)*4+1];
 UCHAR r=buffer[(i*width+j)*4+2];

四、剩余的问题
 当数据取到了,颜色也分离出来了 ,但是可能你绘出的位图是倒转的,这是因为有些位图的确是翻转的。通过bitmapinfoheader的biHeight可以判断是正常还是翻转,当biHeight>0的时候颠倒,它小于0的时候正常,不过测试写到现在看到的文件都是颠倒过来的。

五、相关测试代码:
 采用MFC 目的只是实现自行解析位图文件
void CBmpTestView::OnDraw(CDC* pDC)
{
 CBmpTestDoc* pDoc = GetDocument();
 ASSERT_VALID(pDoc);
 
 // TODO: 在此处为本机数据添加绘制代码
 
 if(filename==""){
  return;
 }
 FILE *fp=fopen(filename,"r");
 if(fp==NULL){
  pDC->TextOut(100,200,"no file found");
  return;
 }
 BITMAPFILEHEADER fileheader;
 BITMAPINFO info;
 
 fread(&fileheader,sizeof(fileheader),1,fp);
 if(fileheader.bfType!=0x4D42){
  pDC->TextOut(100,200,"无位图文件 请选择位图文件");
  fclose(fp);
  return ;
 }
 fread(&info.bmiHeader,sizeof(BITMAPINFOHEADER),1,fp);
 long width=info.bmiHeader.biWidth;
 long height=info.bmiHeader.biHeight;
 UCHAR *buffer=new UCHAR[info.bmiHeader.biSizeImage];
 fseek(fp,fileheader.bfOffBits,0);
 fread(buffer,info.bmiHeader.biSizeImage,1,fp);

if(info.bmiHeader.biBitCount==8){
  int pitch;
  if(width%4==0){
   pitch=width;
  }else{
   pitch=width+4-width%4;
  }
  RGBQUAD quad[256];
  fseek(fp,fileheader.bfOffBits-sizeof(RGBQUAD)*256,0);
  fread(quad,sizeof(RGBQUAD)*256,1,fp);
  if(height>0){
   //height>0 表示图片颠倒
   for(int i=0;i<height;i++){
    for(int j=0;j<width;j++){
     int index=buffer[i*pitch+j];
     UCHAR r=quad[index].rgbRed;
     UCHAR g=quad[index].rgbGreen;
     UCHAR b=quad[index].rgbBlue;
     pDC->SetPixel(j,height-i,RGB(r,g,b));
    }
   }
  }else{
   for(int i=0;i<0-height;i++){
    for(int j=0;j<width;j++){
     int index=buffer[i*pitch+j];
     UCHAR r=quad[index].rgbRed;
     UCHAR g=quad[index].rgbGreen;
     UCHAR b=quad[index].rgbBlue;
     pDC->SetPixel(j,i,RGB(r,g,b));
    }
   }
  }
 }else if(info.bmiHeader.biBitCount==16){
  int pitch=width+width%2;
  if(height>0){
   //height>0 表示图片颠倒
   if(info.bmiHeader.biCompression==BI_RGB){
    //该模式只有555
    for(int i=0;i<height;i++){
     for(int j=0;j<width;j++){   
      //5 5 5 格式
      UCHAR b=buffer[(i*pitch+j)*2]&0x1F;
      UCHAR g=(((buffer[(i*pitch+j)*2+1]<<6)&0xFF)>>3)+(buffer[(i*pitch+j)*2]>>5);
      UCHAR r=(buffer[(i*pitch+j)*2+1]<<1)>>3;
      pDC->SetPixel(j,height-i,RGB((r*0xFF)/0x1F,(g*0xFF)/0x1F,(b*0xFF)/0x1F));
     }
    }
   }else if(info.bmiHeader.biCompression==BI_BITFIELDS){
    //该模式在bitmapinfoheader之后存在RGB掩码 每个掩码1 DWORD
    fseek(fp,fileheader.bfOffBits-sizeof(DWORD )*3,0);
    DWORD  rMask;
    fread(&rMask,sizeof(DWORD ),1,fp);
    if(rMask==0x7C00){
     // 5 5 5 格式
     MessageBeep(0);
     for(int i=0;i<height;i++){
      for(int j=0;j<width;j++){
       UCHAR b=buffer[(i*pitch+j)*2]&0x1F;
       UCHAR g=(((buffer[(i*pitch+j)*2+1]<<6)&0xFF)>>3)+(buffer[(i*pitch+j)*2]>>5);
       UCHAR r=(buffer[(i*pitch+j)*2+1]<<1)>>3;
       pDC->SetPixel(j,height-i,RGB((r*0xFF)/0x1F,(g*0xFF)/0x1F,(b*0xFF)/0x1F));
      }
     }
    }else if(rMask==0xF800){
     //5 6 5 格式
     for(int i=0;i<height;i++){
      for(int j=0;j<width;j++){
       UCHAR b=buffer[(i*pitch+j)*2]&0x1F;
       UCHAR g=(((buffer[(i*pitch+j)*2+1]<<5)&0xFF)>>2)+(buffer[(i*pitch+j)*2]>>5);
       UCHAR r=buffer[(i*pitch+j)*2+1]>>3;
       pDC->SetPixel(j,height-i,RGB(r*0xFF/0x1F,g*0xFF/0x3F,b*0xFF/0x1F));
      }
     }
    }
   }
  }else{
   if(info.bmiHeader.biCompression==BI_RGB){
    //该模式只有555
    for(int i=0;i<0-height;i++){
     for(int j=0;j<width;j++){   
      //5 5 5 格式
      UCHAR b=buffer[(i*pitch+j)*2]&0x1F;
      UCHAR g=(((buffer[(i*pitch+j)*2+1]<<6)&0xFF)>>3)+(buffer[(i*pitch+j)*2]>>5);
      UCHAR r=(buffer[(i*pitch+j)*2+1]<<1)>>3;
      pDC->SetPixel(j,i,RGB((r*0xFF)/0x1F,(g*0xFF)/0x1F,(b*0xFF)/0x1F));
     }
    }
   }else if(info.bmiHeader.biCompression==BI_BITFIELDS){
    //该模式在bitmapinfoheader之后存在RGB掩码 每个掩码1 DWORD
    fseek(fp,fileheader.bfOffBits-sizeof(DWORD )*3,0);
    DWORD  rMask;
    fread(&rMask,sizeof(DWORD ),1,fp);
    if(rMask==0x7C00){
     // 5 5 5 格式
     MessageBeep(0);
     for(int i=0;i<0-height;i++){
      for(int j=0;j<width;j++){
       UCHAR b=buffer[(i*pitch+j)*2]&0x1F;
       UCHAR g=(((buffer[(i*pitch+j)*2+1]<<6)&0xFF)>>3)+(buffer[(i*pitch+j)*2]>>5);
       UCHAR r=(buffer[(i*pitch+j)*2+1]<<1)>>3;
       pDC->SetPixel(j,i,RGB((r*0xFF)/0x1F,(g*0xFF)/0x1F,(b*0xFF)/0x1F));
      }
     }
    }else if(rMask==0xF800){
     //5 6 5 格式
     for(int i=0;i<0-height;i++){
      for(int j=0;j<width;j++){
       UCHAR b=buffer[(i*pitch+j)*2]&0x1F;
       UCHAR g=(((buffer[(i*pitch+j)*2+1]<<5)&0xFF)>>2)+(buffer[(i*pitch+j)*2]>>5);
       UCHAR r=buffer[(i*pitch+j)*2+1]>>3;
       pDC->SetPixel(j,i,RGB(r*0xFF/0x1F,g*0xFF/0x3F,b*0xFF/0x1F));
      }
     }
    }
   }
  }
  //pDC->TextOut(100,200,"16位图");
 }else if(info.bmiHeader.biBitCount==24){
  int pitch=width%4;
  //b g r
  if(height>0){
   //height>0 表示图片颠倒
   for(int i=0;i<height;i++){
    int realPitch=i*pitch;
    for(int j=0;j<width;j++){     
     UCHAR b=buffer[(i*width+j)*3+realPitch];
     UCHAR g=buffer[(i*width+j)*3+1+realPitch];
     UCHAR r=buffer[(i*width+j)*3+2+realPitch];
     pDC->SetPixel(j,height-i,RGB(r,g,b));
    }
   }
  }else{
   for(int i=0;i<0-height;i++){
    int realPitch=i*pitch;
    for(int j=0;j<width;j++){
     UCHAR b=buffer[(i*width+j)*3+realPitch];
     UCHAR g=buffer[(i*width+j)*3+1+realPitch];
     UCHAR r=buffer[(i*width+j)*3+2+realPitch];
     pDC->SetPixel(j,i,RGB(r,g,b));
    }
   }
  }
  
  //pDC->TextOut(100,200,"24位图");

}else if(info.bmiHeader.biBitCount==32){
  // b g r a
  if(height>0){
   //height>0 表示图片颠倒
   for(int i=0;i<0-height;i++){
    for(int j=0;j<width;j++){
     UCHAR b=buffer[(i*width+j)*4];
     UCHAR g=buffer[(i*width+j)*4+1];
     UCHAR r=buffer[(i*width+j)*4+2];
     pDC->SetPixel(j,height-i,RGB(r,g,b));
    }
   }
  }else{
   for(int i=0;i<height;i++){
    for(int j=0;j<width;j++){
     UCHAR b=buffer[(i*width+j)*4];
     UCHAR g=buffer[(i*width+j)*4+1];
     UCHAR r=buffer[(i*width+j)*4+2];
     pDC->SetPixel(j,i,RGB(r,g,b));
    }
   }
  }
  //pDC->TextOut(100,200,"32位图");
 }
 delete buffer;
 fclose(fp);
}

CreateDIBSection
理解分辨率
我们常说的屏幕分辨率为640×480,刷新频率为70Hz,意思是说每行要扫描640个象素,一共有480行,每秒重复扫描屏幕70次。
 
理解调色板
有一个长宽各为200个象素,颜色数为16色的彩色图,每一个象素都用R、G、B三个分量表示。因为每个分量有256个级别,要用8位(bit),即一个字节(byte)来表示,所以每个象素需要用3个字节。整个图象要用200×200×3,约120k字节,可不是一个小数目呀!如果我们用下面的方法,就能省的多。
因为是一个16色图,也就是说这幅图中最多只有16种颜色,我们可以用一个表:表中的每一行记录一种颜色的R、G、B值。这样当我们表示一个象素的颜色时,只需要指出该颜色是在第几行,即该颜色在表中的索引值。举个例子,如果表的第0行为255,0,0(红色),那么当某个象素为红色时,只需要标明0即可。
让我们再来计算一下:16种状态可以用4位(bit)表示,所以一个象素要用半个字节。整个图象要用200×200×0.5,约20k字节,再加上表占用的字节为3×16=48字节.整个占用的字节数约为前面的1/6,省很多吧?
这张R、G、B的表,就是我们常说的调色板(Palette),另一种叫法是颜色查找表LUT(Look Up Table),似乎更确切一些。Windows位图中便用到了调色板技术。其实不光是Windows位图,许多图象文件格式如pcx、tif、gif等都用到了。所以很好地掌握调色板的概念是十分有用的。
有一种图,它的颜色数高达256×256×256种,也就是说包含我们上述提到的R、G、B颜色表示方法中所有的颜色,这种图叫做真彩色图(true color)。真彩色图并不是说一幅图包含了所有的颜色,而是说它具有显示所有颜色的能力,即最多可以包含所有的颜色。表示真彩色图时,每个象素直接用R、G、B三个分量字节表示,而不采用调色板技术。原因很明显:如果用调色板,表示一个象素也要用24位,这是因为每种颜色的索引要用24位(因为总共有224种颜色,即调色板有224行),和直接用R,G,B三个分量表示用的字节数一样,不但没有任何便宜,还要加上一个256×256×256×3个字节的大调色板。所以真彩色图直接用R、G、B三个分量表示,它又叫做24位色图。
bmp文件格式
介绍完位图和调色板的概念,下面就让我们来看一看Windows的位图文件(.bmp文件)的格式是什么样子的。
bmp文件大体上分成四个部分,如图1.3所示。
位图文件头BITMAPFILEHEADER
位图信息头BITMAPINFOHEADER
调色板Palette
实际的位图数据ImageDate
图1.3     Windows位图文件结构示意图
第一部分为位图文件头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
为从文件头到实际的位图数据的偏移字节数,即图1.3中前三个部分的长度之和。
第二部分为位图信息头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(真彩色图)(新的.bmp格式支持32位色,这里就不做讨论了)。
biCompression
指定位图是否压缩,有效的值为BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS(都是一些Windows定义好的常量)。要说明的是,Windows位图可以采用RLE4,和RLE8的压缩格式,但用的不多。我们今后所讨论的只有第一种不压缩的情况,即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
指定目标设备的水平分辨率,单位是每米的象素个数,关于分辨率的概念,我们将在第4章详细介绍。
biYPelsPerMeter
指定目标设备的垂直分辨率,单位同上。
biClrUsed
指定本图象实际用到的颜色数,如果该值为零,则用到的颜色数为2biBitCount
biClrImportant
指定本图象中重要的颜色数,如果该值为零,则认为所有的颜色都是重要的。
第三部分为调色板Palette,当然,这里是对那些需要调色板的位图文件而言的。有些位图,如真彩色图,前面已经讲过,是不需要调色板的,BITMAPINFOHEADER后直接是位图数据。
调色板实际上是一个数组,共有biClrUsed个元素(如果该值为零,则有2biBitCount个元素)。数组中每个元素的类型是一个RGBQUAD结构,占4个字节,其定义如下:
typedef struct tagRGBQUAD {
BYTE    rgbBlue; //该颜色的蓝色分量
BYTE    rgbGreen; //该颜色的绿色分量
BYTE    rgbRed; //该颜色的红色分量
BYTE    rgbReserved; //保留值
} RGBQUAD;
第四部分就是实际的图象数据了。对于用到调色板的位图,图象数据就是该象素颜在调色板中的索引值。对于真彩色图,图象数据就是实际的R、G、B值。下面针对2色、16色、256色位图和真彩色位图分别介绍。
对于2色位图,用1位就可以表示该象素的颜色(一般0表示黑,1表示白),所以一个字节可以表示8个象素。
对于16色位图,用4位可以表示一个象素的颜色,所以一个字节可以表示2个象素。
对于256色位图,一个字节刚好可以表示1个象素。
对于真彩色图,三个字节才能表示1个象素,哇,好费空间呀!没办法,谁叫你想让图的颜色显得更亮丽呢,有得必有失嘛。
要注意两点:
(1)    每一行的字节数必须是4的整倍数,如果不是,则需要补齐。这在前面介绍biSizeImage时已经提到了。
(2)    一般来说,.bMP文件的数据从下到上,从左到右的。也就是说,从文件中最先读到的是图象最下面一行的左边第一个象素,然后是左边第二个象素……接下来是倒数第二行左边第一个象素,左边第二个象素……依次类推 ,最后得到的是最上面一行的最右一个象素。
下面的函数将pBuffer指向的内存块中的位图数据写入文件中,lBufferLen参数为pBuffer指向的内存块的大小,注意必须先指定位图的BITMAPFILEHEADER结构和BITMAPINFOHEADER结构。
STDMETHODIMP CSampleGrabberCallback::BufferCB(double time,BYTE* pBuffer,long lBufferLen)
{
    if(!g_bSnap)
        return E_FAIL;
    BOOL bWrite=FALSE;
    HANDLE hFile=CreateFile("E://Test.bmp",GENERIC_WRITE,
        FILE_SHARE_READ, NULL, CREATE_ALWAYS, NULL, NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {
        return E_FAIL;
}
//首先初始化位图文件头结构(BITMAPFILEHEADER),并将其写入文件。
    BITMAPFILEHEADER bmpFileHeader;
    //memset(&bmpFileHeader,0,sizeof(bmpFileHeader));
    ZeroMemory(&bmpFileHeader,sizeof(bmpFileHeader));
 
    bmpFileHeader.bfType='MB';
    bmpFileHeader.bfSize=sizeof(bmpFileHeader)+lBufferLen+sizeof(BITMAPINFOHEADER);
    bmpFileHeader.bfOffBits=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);
 
    DWORD dwWritten=0;
    bWrite=WriteFile(hFile,&bmpFileHeader,sizeof(bmpFileHeader),&dwWritten,NULL);
    if(!bWrite)
    {
        MessageBox(0,TEXT("fail to write"),TEXT("Error"),MB_OK);
    }
 
    //初始化BITMAPINFOHEADER结构并将其写入文件。
    //VIDEOINFOHEADER*viInfoHeader=(VIDEOINFOHEADER*) g_media_type.pbFormat;
    //FreeMediaType(g_media_type);
    BITMAPINFOHEADER bmpInfoHeader;
    ZeroMemory(&bmpInfoHeader,sizeof(bmpInfoHeader));
    //memset(&bmpInfoHeader,0,sizeof(bmpInfoHeader));
 
    bmpInfoHeader.biSize=sizeof(bmpInfoHeader);
    bmpInfoHeader.biWidth=lWidth;
    bmpInfoHeader.biHeight=lHeight;
    bmpInfoHeader.biPlanes=1;
    bmpInfoHeader.biBitCount=16;//???24 8
 
    dwWritten=0;
    bWrite=WriteFile(hFile,&bmpInfoHeader,sizeof(bmpInfoHeader),&dwWritten,NULL);
    if(!bWrite)
    {
        MessageBox(0,TEXT("fail to write"),TEXT("Error"),MB_OK);
    }
 
    //最后将位图的主要数据写入文件。
    dwWritten=0;
    bWrite=WriteFile(hFile,pBuffer,lBufferLen,&dwWritten,NULL);
    if(!bWrite)
    {
        MessageBox(0,TEXT("fail to write"),TEXT("Error"),MB_OK);
    }
 
    CloseHandle(hFile);
 
    CWnd* pMainWnd=theApp.GetMainWnd();
    CDfgDlg* pDfg=(CDfgDlg*)pMainWnd;
    HWND hwnd=pDfg->m_picture.GetSafeHwnd();
   
    RECT rc;
    ::GetWindowRect(hwnd,&rc);
    long lStillWidth=rc.right-rc.left;
    long lStillHeight=rc.bottom-rc.top;
    
    HDC hdcStill=GetDC(hwnd);
    PAINTSTRUCT ps;
    BeginPaint(hwnd,&ps);
 
SetStretchBltMode(hdcStill,COLORONCOLOR);
 StretchDIBits(hdcStill,0,0,lStillWidth,lStillHeight,0,0,lWidth,lHeight,pBuffer,(BITMAPINFO*)&bmpInfoHeader,DIB_RGB_COLORS,SRCCOPY);
    EndPaint(hwnd,&ps);
    ReleaseDC(hwnd,hdcStill);
 
    g_bSnap=!g_bSnap;
    return S_OK;   
}
 
 
关于CreateDIBSection函数:
HBITMAP CreateDIBSection(
 HDC hdc,                 // handle to DC
 CONST BITMAPINFO*pbmi, // bitmap data
 UINT iUsage,             // data type indicator
 VOID**ppvBits,          // bit values
 HANDLE hSection,         // handle to file mapping object
 DWORD dwOffset           // offset to bitmap bit values
);
 
CreateDIBSection函数会根据位图结构信息(pbmi)分配内存空间,你不用为它分配内存,这块内存也不需要你释放,系统会自己释放的。    
  然后将位图中的图像数据读入这个内存地址,显示即可。    
  LPBYTE   lpBits; 
  HBITMAP   hBmp=::CreateDIBSection(dcMem.m_hDC,lpBitmap,DIB_PAL_COLORS,   &lpBits,NUL      L,0);
//将图像数据填充到得到的内存地址中
  file.ReadHuge(lpBits,dwBitlen);   
  pDC->StretchBlt(0,0,bmp.bmWidth,bmp.bmHeight,&dcMem,0,0,   
  bmp.bmWidth,bmp.bmHeight,SRCCOPY);  
 
首先让我们检查一下如何简化CreateDIBSection,并正确地使用它。首先,把最後两个参数hSection和dwOffset,分别设定为NULL和0,我将在本章最後讨论这些参数的用法。第二,仅在fColorUse参数设定为DIB_ PAL_COLORS时,才使用hdc参数,如果fColorUse为DIB_RGB_COLORS(或0),hdc将被忽略(这与CreateDIBitmap不同,hdc参数用於取得与DDB相容的设备的色彩格式,CreateDIBitmap创建的是DDB(设备相关位图,CreateDIBSection创建设备无关位图),因此必须指定与位图所关联的设备,即hdc,位图根据hdc所代表的设备来取得位图的色彩格式)。
因此,CreateDIBSection最简单的形式仅需要第二和第四个参数。第二个参数是指向BITMAPINFO结构的指标,
BITMAPINFOHEADER         bmih ;
BYTE                           * pBits ;
HBITMAP                          hBitmap ;
现在初始化BITMAPINFOHEADER结构的栏位

bmih->biSize                  = sizeof (BITMAPINFOHEADER) ;
bmih->biWidth                 = 384 ;
bmih->biHeight                = 256 ;
bmih->biPlanes                = 1 ;
bmih->biBitCount              = 24 ;
bmih->biCompression           = BI_RGB ;
bmih->biSizeImage             = 0 ;
bmih->biXPelsPerMeter         = 0 ;
bmih->biYPelsPerMeter         = 0 ;
bmih->biClrUsed               = 0 ;
bmih->biClrImportant          = 0 ;
在基本准备後,我们呼叫该函式:

hBitmap = CreateDIBSection (NULL, (BITMAPINFO *)  &bmih, 0, &pBits, NULL, 0) ;

这是函式呼叫所做的:CreateDIBSection检查BITMAPINFOHEADER结构并配置足够的记忆体块来载入DIB图素位元。(在这个例子里,记忆体块的大小为384×256×3位元组。)它在您提供的pBits参数中储存了指向此记忆体块的指标。
然而,我们还没有做完,点阵图图素是未初始化的。如果正在读取DIB档案,可以简单地把pBits参数传递给ReadFile函式并读取它们。或者可以使用一些程式码「人工」设定。
注意:使用CreateDIBSection函数获得的内存块指针(输出的第四个参数)所指向的地址中是没有内容的,我们必须向里面写入图像数据,然后才能够显示图像。

位图文件(BMP)格式分析相关推荐

  1. 【转】glTexImage2D()和gluBuild2DMipmaps() [将载入的位图文件(*.bmp)转换成纹理贴图]+glTexParameteri()纹理过滤函数...

    glTexImage2D()和gluBuild2DMipmaps() 说明:两者的都是生成纹理,即:将载入的位图文件(*.bmp)转换成纹理贴图. glTexImage2D()的用法举例 glTexI ...

  2. c++ 24位bmp格式分析

    问题:现有一张bmp图片,要求将它读取到程序中并进行灰度化.水平翻转.模糊.茶色滤镜四种效果的一种,并输出新图片,如下所示: 命令行输入: 其中: 参数1:-b/g/s/r,先后表示blur(模糊), ...

  3. 《TencentNCNN系列》 之bin文件(网络参数文件)格式分析

    #PS:要转载请注明出处,本人版权所有 #PS:这个只是 < 我自己 >理解,如果和你的 #原则相冲突,请谅解,勿喷 时间:2018.07.18 ncnn master commit id ...

  4. Hadoop文件压缩格式分析和比较

    Hadoop支持压缩格式: 压缩格式 可分割 算法 扩展名 Linux工具 gzip 否 DEFLATE .gz gzip lzo 是(加索引) LZO .lzo lzop snappy 否 Snap ...

  5. matlab产生bmp图片,matlab 生成.bmp格式的文件

    生成.bmp格式的文件 .bmp格式的图片是未压缩的图片,相比于.raw格式,需要加上头文件.下面以大小为M*N的图片为例,说明头文件格式和内容. M = 128; N = 128; % 构造头文件 ...

  6. BMP格式知识之三:bmp格式的编解码

    bmp格式的编解码 BMP是英文Bitmap(位图)的简写,它是Windows操作系统中的标准图像文件格式,能够被多种Windows应用程序所支持.随着Windows操作系统的流行与丰富的Window ...

  7. zt BMP 文件格式分析

    看到这文章不错,于是帖了上来.呵呵 BMP文件格式分析(zz) 前两天要做一个读取bmp文件的小程序,顺便查找了一些关于BMP格式的文章,现在post上来. 简介 BMP(Bitmap-File)图形 ...

  8. VS2015实现bmp格式图片的读取

    预备知识:掌握一些mfc基本的控件用法以及bmp格式的图片的一些内容,当然不知道也无伤大雅. step1:新建基于对话框的mfc工程 step2:拖控件 然后将PictureControl的ID随便改 ...

  9. BMP文件格式分析(zz)

    前两天要做一个读取bmp文件的小程序,顺便查找了一些关于BMP格式的文章,现在post上来. 简介 BMP(Bitmap-File)图形文件是Windows采用的图形文件格式,在Windows环境下运 ...

最新文章

  1. Cesium调用天地图的新问题
  2. sklearn应用—高斯混合
  3. vue模板html,VueJS模板
  4. linux r后台执行,screen 命令简单用法 Linux后台执行 就用它
  5. 因为我们一直强调选品的重要性
  6. Redis的使用原理
  7. 如何在Qt Creator中导入图标资源
  8. 高仿QQ顶部控件之IOS SegmentView
  9. 控制用户创建课程权限
  10. 国内免费CMS系统大全
  11. SEO与SEM有什么区别?
  12. 数据分析相关职位分析与可视化
  13. 少年Pi的奇幻漂流-我们的后台自动化发布方案
  14. [HNOI2004]宠物收养所
  15. 外卖订单语音通知功能如何实现?(附外卖订单语音通知模板)
  16. pannel加载form
  17. 开源中国部分源代码分享
  18. Matlab笔记 第二章 基本操作与矩阵输入
  19. Web前端:古诗排版网页案例设计
  20. outlook gmail_将您的Gmail帐户添加到Outlook 2007

热门文章

  1. C#实现域账号密码登录
  2. 中国热泵热水器行业发展趋势及投资风险研究报告
  3. 图像处理:直方图规定化
  4. 总谐波失真80_总谐波失真(THD)
  5. 为什么使用dojo?dojo与jquery有什么不同?dojo适合什么开发场景?
  6. 对汽车供应商的评估需要哪些数据?
  7. spring 的@PersistenceUnit和@PersistenceContext
  8. 浏览器选择 html,select的最佳预设打造全兼容各浏览器select
  9. 使用 lasio 处理 .las 文件
  10. Java API常用package介绍