使用C++读取8位BMP位图

使用C++读取8位BMP位图

一、 基础知识

  1. 微软的图像库关于位图的数据结构BITMAPFILEHEADER、BITMAPINFOHEADER
  2. 位图存储时候的四字节对齐。
  3. 调色板的理解

二、八位图片的读取

细节见代码和注释。需要注意的是我们实际读的时候忽略了填充的字节(没必要)。


BYTE *RmwRead8BitBmpFile2Img(const char * filename,int *width,int *height){
    FILE *binFile;
    BYTE *pImg=NULL;
    BITMAPFILEHEADER fileHeader;
    BITMAPINFOHEADER bmpHeader;
    BOOL isRead=TRUE;
    int linenum,ex; //linenum:一行像素的字节总数,包括填充字节 

    //open file
    if((binFile=fopen(filename,"rb"))==NULL) return NULL;

    //read struts
    if(fread((void *)&fileHeader,1,sizeof(fileHeader),binFile)!=sizeof(fileHeader)) isRead=FALSE;
    if(fread((void *)&bmpHeader,1,sizeof(bmpHeader),binFile)!=sizeof(bmpHeader)) isRead=FALSE;

    //问,这里的判断是为了避免什么样问题
    if(isRead==FALSE||fileHeader.bfOffBits<sizeof(fileHeader)+sizeof(bmpHeader)){
        fclose(binFile);
        return NULL;
    }

    //read image info
    *width=bmpHeader.biWidth;
    *height=bmpHeader.biHeight;
    linenum=(*width*1+3)/4*4;
    ex=linenum-*width*1;         //每一行的填充字节

    fseek(binFile,fileHeader.bfOffBits,SEEK_SET);
    pImg=new BYTE[(*width)*(*height)];
    if(pImg!=NULL){
        for(int i=0;i<*height;i++){
            int r=fread(pImg+(*height-i-1)*(*width),sizeof(BYTE),*width,binFile);
            if(r!=*width){
                delete pImg;
                fclose(binFile);
                return NULL;
            }
            fseek(binFile,ex,SEEK_CUR);
        }
    }
    fclose(binFile);
    return pImg;
}

三、八位图片的存储

下面给出的代码用调色板把原图Fig.1变成Fig.2的样子。这么做是为了强调调色板的作用。如果需要正常的存8位图稍微修改调色板那一部分(下代码中注释了)就行了。

bool RmwWriteByteImg2BmpFile(BYTE *pImg,int width,int height,const char * filename)
{   FILE * BinFile;
    BITMAPFILEHEADER FileHeader;
    BITMAPINFOHEADER BmpHeader;
    int i,extend;
    bool Suc=true;
    BYTE p[4],*pCur;
    BYTE* ex;

    extend=(width+3)/4*4-width;

    // Open File
    if((BinFile=fopen(filename,"w+b"))==NULL) {  return false; }
    //参数填法见结构链接
    FileHeader.bfType= ((WORD) ('M' << 8) | 'B');
    FileHeader.bfOffBits=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+256*4L;//2个头结构后加调色板
    FileHeader.bfSize=FileHeader.bfOffBits+(width+extend)*height;
    FileHeader.bfReserved1=0;
    FileHeader.bfReserved2=0;
    if (fwrite((void *)&FileHeader,1,sizeof(FileHeader),BinFile)!=sizeof(FileHeader)) Suc=false;
    // Fill the ImgHeader
    BmpHeader.biSize = 40;
    BmpHeader.biWidth = width;
    BmpHeader.biHeight = height;
    BmpHeader.biPlanes = 1 ;
    BmpHeader.biBitCount = 8 ;
    BmpHeader.biCompression = 0 ;
    BmpHeader.biSizeImage = 0 ;
    BmpHeader.biXPelsPerMeter = 0;
    BmpHeader.biYPelsPerMeter = 0;
    BmpHeader.biClrUsed = 0;
    BmpHeader.biClrImportant = 0;
    if (fwrite((void *)&BmpHeader,1,sizeof(BmpHeader),BinFile)!=sizeof(BmpHeader)) Suc=false;
    // 写入调色板
    for (i=0,p[3]=0;i<256;i++) 
    {  
       p[0]=p[1]=p[2]=255-i; // blue,green,red;
       if (fwrite((void *)p,1,4,BinFile)!=4) { Suc=false; break; }
    }


    if(extend)
    {
        ex=new BYTE[extend]; //填充数组大小为 0~3
        memset(ex,0,extend);
    }

    //write data
    for(pCur=pImg+(height-1)*width;pCur>=pImg;pCur-=width)
    {   
        if (fwrite((void *)pCur,1,width,BinFile)!=(unsigned int)width) Suc=false; // 真实的数据
        if(extend) // 扩充的数据 这里填充0
            if (fwrite((void *)ex,1,extend,BinFile)!=1) Suc=false;
    }

    // return;
    fclose(BinFile);
    if(extend)
        delete[] ex;
    return Suc;
}

四、主调函数和结果

主调函数:

#include
#include
int main()
{
    int width,height,rwidth;
    BYTE *pFile;
    pFile=RmwRead8BitBmpFile2Img("D:\\test.bmp",&width,&height);
    RmwWriteByteImg2BmpFile(pFile,width,height,"D:\\test1.bmp");
}


使用C++读取8位BMP位图_第1张图片

Fig.1

使用C++读取8位BMP位图_第2张图片

Fig.2



五、说明

  1. 需要注意的地方是字节对齐而不是宽度对齐图像一个像素宽度在位深不同的情况下是不一样的。在8位图处理的时候宽度被4整除和字节被4整除等价(一个像素宽度对应一个字节),但是在24位的时候一个像素宽度由3个字节表示。所以这个时候不能以宽度为基准了。比如此时宽度为2,正确填充方式是一行有2*3=6个字节,再填充2个字节能被4整除。如果以宽度为基准,像素宽度扩为4,则填充了4*3-2*3=6个字节。可见结果是不同的。前一种是正确的处理方式。
  2. 24位写的时候不需要写入调色板了。

你可能感兴趣的:(图像处理)