• 关于BMP


    关于BMP位图的资料网上有很多,内容也比较基础。本文实现BMP位图的读取、显示、保存,并对一些重要的问题进行说明(包括字节对齐、内存中的存储顺序、调色板)。

    BMP文件共包括文件头、信息头、调色板(位深<=8的图像含有此项)、位图数据四大部分:

    各部分的具体说明可以参考[1]

    下面是位图的读取、显示、保存实现的主体代码。(完整工程下载:Bmptest

    CString Filename;
    CStatic Picture;
    long Width ; 
    long Height ; 
    unsigned short BitCount; 
    long Stride;
    unsigned char* Img;
    void OpenBmp();
    void SaveBmp();
    
    

    //打开位图
    void CBmptestDlg::OpenBmp()
    {
    CRect zcRect;
    Picture.GetClientRect(&zcRect);
    CDC* pDC=Picture.GetDC();

    
    

    BITMAPFILEHEADER header;//文件头
    BITMAPINFOHEADER infoheader;//信息头
    BITMAPINFO *bitmapinfo=NULL;

    
    

    FILE *fp=fopen(Filename,"rb");
    if(fp==NULL){return;}
    fread(&header,sizeof(BITMAPFILEHEADER),1,fp);
    if(header.bfType != 0x4D42){return;}
    fread(&infoheader,sizeof(BITMAPINFOHEADER),1,fp);
    Width = infoheader.biWidth;
    Height = infoheader.biHeight>0?infoheader.biHeight:-infoheader.biHeight;
    BitCount = infoheader.biBitCount;
    Stride=((Width*BitCount+31)>>5)<<2;//一行字节数,4字节对齐
    int Imgsize=Stride*Height;
    if (Img!=NULL){free(Img);}
    Img=(unsigned char*)malloc(Imgsize);
    if(BitCount<=8)
    {
    int Palettesize=header.bfOffBits-sizeof(BITMAPFILEHEADER)-sizeof(BITMAPINFOHEADER);//不能PaletteLen=1<<biBitCount,因调色板大小可在[2,256]取值
    RGBQUAD *Palette=(RGBQUAD*)malloc(Palettesize);
    fread(Palette,Palettesize,1,fp);
    bitmapinfo=(BITMAPINFO*)malloc(sizeof(BITMAPINFOHEADER)+Palettesize);
    bitmapinfo->bmiHeader=infoheader;
    memcpy(bitmapinfo->bmiColors,Palette,Palettesize);
    fread(Img,Imgsize,1,fp);
    free(Palette);
    }

    if(BitCount>=16)
    {
    bitmapinfo=(BITMAPINFO*)malloc(sizeof(BITMAPINFOHEADER));
    bitmapinfo->bmiHeader=infoheader;
    fread(Img,Imgsize,1,fp);
    }

    
    

    SetStretchBltMode(pDC->m_hDC,COLORONCOLOR);
    StretchDIBits(pDC->m_hDC,0,0,zcRect.Width(),zcRect.Height(),0,0,Width,Height,Img,bitmapinfo,DIB_RGB_COLORS,SRCCOPY);
    ReleaseDC(pDC);
    free(bitmapinfo);
    fclose(fp);
    return;
    }

    
    

    //保存位图
    //常见需求是由位图数据、宽、高、位深将其保存为位图,故此函数只考虑8位灰度,162432彩色位图
    void CBmptestDlg::SaveBmp()
    {
    if(BitCount<8)return;
    if(Img==NULL)return;
    FILE*fp=fopen(Filename,"wb");
    if(fp==NULL)return;
    BITMAPFILEHEADER hearder;
    BITMAPINFOHEADER infohearder;
    int ImgSize=Stride*Height;
    if (BitCount==8)
    {
    int PaletteSize=sizeof(RGBQUAD)*256;
    hearder.bfType=0X4D42;
    hearder.bfSize=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+PaletteSize+ImgSize;//文件总大小
    hearder.bfReserved1=0;
    hearder.bfReserved2=0;
    hearder.bfOffBits=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+PaletteSize;
    fwrite(&hearder,sizeof(BITMAPFILEHEADER),1,fp);

    
    

    infohearder.biSize=sizeof(BITMAPINFOHEADER);
    infohearder.biWidth=Width;
    infohearder.biHeight=Height;//倒序
    //infohearder.biHeight=-Height;//顺序
    infohearder.biPlanes=1;
    infohearder.biBitCount=BitCount;
    infohearder.biCompression=BI_RGB;
    infohearder.biSizeImage=ImgSize;
    infohearder.biXPelsPerMeter = 0;
    infohearder.biYPelsPerMeter = 0;
    infohearder.biClrUsed = 0;
    infohearder.biClrImportant = 0;
    fwrite(&infohearder,sizeof(BITMAPINFOHEADER),1,fp);

    
    

    RGBQUAD * palette=(RGBQUAD*)malloc(PaletteSize);
    for (int i=0;i<256;i++) //这里针对8位灰度图
    {
    palette[i].rgbRed=palette[i].rgbGreen=palette[i].rgbBlue=i;
    palette[i].rgbReserved=0;
    }
    fwrite(palette,PaletteSize,1,fp);
    fwrite(Img,ImgSize,1,fp);
    }

    
    

    if(BitCount>=16)
    {
    hearder.bfType=0X4D42;
    hearder.bfSize=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+ImgSize;//文件总大小
    hearder.bfReserved1=0;
    hearder.bfReserved2=0;
    hearder.bfOffBits=sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);
    fwrite(&hearder,sizeof(BITMAPFILEHEADER),1,fp);

    
    

    infohearder.biSize=sizeof(BITMAPINFOHEADER);
    infohearder.biWidth=Width;
    infohearder.biHeight=Height;//倒序
    //infohearder.biHeight=-Height;//顺序
    infohearder.biPlanes=1;
    infohearder.biBitCount=BitCount;
    infohearder.biCompression=BI_RGB;
    infohearder.biSizeImage=ImgSize;
    infohearder.biXPelsPerMeter = 0;
    infohearder.biYPelsPerMeter = 0;
    infohearder.biClrUsed = 0;
    infohearder.biClrImportant = 0;
    fwrite(&infohearder,sizeof(BITMAPINFOHEADER),1,fp);
    fwrite(Img,ImgSize,1,fp);
    }
    fclose(fp);
    }

     

    界面:

     关于四字节对齐

    Windows为了存取的效率,规定位图存储时必须满足一行为4字节的整数倍。我们处理位图时通常需要求取一行对应的字节数,需要使用如下公式:

    另一个常用的公式但并不适用于位深为1、4的位图:

    原因在于第二个公式是以字节为单位考虑的,而第一个公式以位为单位考虑。

    关于图像存储顺序:

    位图信息头BITMAPINFOHEADER中的biHeight不仅体现位图高度,还标记此位图的存储方式。对于一幅位图:

    若biHeight>0,则内存中存储顺序如下,在该模式下,内存中第一字节实际对应位图的左下角,大部分位图都按此方式存储。

    若biHeight<0,则内存中存储顺序如下,内存第一字节对应位图左上角。

    此类情况模式常用的场合是:用户自己产生一幅图像,譬如在数据采集系统中常常需要将数据进行图像显示。这时用户需要开辟一块内存并按一定方式填充该内存。由于顺序存储方式对用户来说更加直观,操作更加方便,所有常使用第二种模式。这时,在显示和保存位图时将BITMAPINFOHEADER中的biHeight设为负数即可。

    关于索引图像

    位深<=8位的位图才有调色板。本在编程时犯过一个错误,就是误认为8位深度的索引图调色板中就含有256(即2^8)种颜色,在读取调色板数据时若按此长度读会导致最终显示图像时发生偏移。后发现调色板颜色数可以是[2,256]范围内的任何值。

    如在PS中(图像—》模式—》索引颜色)可以设置索引颜色数20:

    实际经测试可以发现调色板中包含21项:

    0:( 20, 50, 26)

    1:( 45, 77, 44)

    2:( 9, 19,  8)

    3:( 12, 40, 8)

    4:( 19, 61, 8)

    5:( 72,107, 61)

    6:( 37, 82, 20)

    7:( 60,110, 32)

    8:(104,127, 88)

    9:( 90,139, 51)

    10:(135,160, 86)

    11:( 45, 48, 18)

    12:(174,171,134)

    13:( 85, 68, 39)

    14:(245,195,163)

    15:(246,127, 75)

    16:(129, 77, 56)

    17:(241, 62, 22)

    18:(125, 28, 11)

    19:(173, 20,  9)

    20:( 0,  0,  0)

    另外一点,调色板类型RGBQUAD定义为:

    typedef structtagRGBQUAD {

            BYTE    rgbBlue;

            BYTE    rgbGreen;

            BYTE    rgbRed;

            BYTE    rgbReserved;

    } RGBQUAD;

    即每一项四字节表示,每一字节分别表示R、G、B、A分量。这一点是重要的,也就是说在自定义调色板数据时,不管位深是1、4、8,调色板中每一分量都是在[0,255]间变化,而与位深大小无关。关于索引图像有个帖子可以参考一下[2]

    参考:

    [1]http://www.cnblogs.com/xiehy/archive/2011/06/07/2074405.html

    [2]http://bbs.csdn.net/topics/110048102

  • 相关阅读:
    QQ
    本周最新文献速递20220410
    linux: x86_64condalinuxgnugcc: No such file or directory报错
    本周最新文献速递20220423
    R语言:group_by, summarise, arrange, slice
    plink: 计算多个SNP集的连锁不平衡(ld)
    perl: 安装 Math::GSL::SF 模块
    写出可复用代码的基本思想与实践
    理解设计模式之“道”
    如丝般顺滑:DDD再实践之类目树管理
  • 原文地址:https://www.cnblogs.com/luo-peng/p/5555176.html
Copyright © 2020-2023  润新知