0xAA55 发表于 2014-2-27 22:20:20

【C】Windows下C语言读取BMP文件并通过GDI显示的方法

我这里只是讲怎么读取BMP文件,这个帖子并不讲BMP文件的详细格式。
BMP文件的详细格式请到这里收看:
http://www.0xaa55.com/thread-271-1-1.html


首先BMP的文件头很简单,一个BITMAPFILEHEADER和一个BITMAPINFOHEADER,和可能出现的调色板,和颜色数据。(如果是图种的话,颜色数据后面会有个压缩包。这个这个……偏题了)


读取BMP的大致流程是先读取BITMAPFILEHEADER和BITMAPINFOHEADER,然后再调用CreateDIBSection创建一个HBITMAP,就可以载入完整的位图了。
或者用更简单的方法,直接StretchDIBits把位图画到HDC上。


我这里就讲讲两个常用的方法:


1、用CreateDIBSection创建一个HBITMAP,然后把HBITMAP选入HDC,再用BitBlt来画。
好处是你想画的时候随时可以画。画的时候效率较高而且速度快。
坏处是麻烦。如果你的程序足够大,用了很多HBITMAP、HDC,你会觉得一团糟。
2、直接用StretchDIBits来画。
好处是很直接,不需要创建HBITMAP、HDC,而且支持图像的缩放,还支持PNG、JPG的显示(这个以后我会教大家怎么显示JPG和PNG的)
坏处是你需要专门分配、管理一个用来存储位图信息的内存块。而且这个方法慢。


方法1的C语言实现:(这里只讲了读取的流程,我并没有测试代码,请不要照抄代码免得出BUG。)//需要用到的头文件:
#include<stdio.h>
#include<windows.h>

//定义一个包含256个调色板的结构体,来读取带调色板的BITMAPINFO
typedef struct
{
        BITMAPINFOHEADER        bmiHeader;
        RGBQUAD                                bmiColors;
}BITMAPINFO_;//类似于BITMAPINFO

//读取BMP的函数
HBITMAP ReadBMPFile(HDC hDC,char*pszBMPFile,void**ppBits)
{
        BITMAPFILEHEADER        BMFH;
        BITMAPINFO_                        BMIF;
        HBITMAP                                hBMPRet=NULL;//要返回的HBITMAP
        UINT                                uPaletteColors=0;//调色板颜色数
        UINT                                iUsage=DIB_RGB_COLORS;//是否为调色板颜色
        FILE                                *fp=fopen(pszBMPFile,"rb");//打开文件
       
        if(!fp)//如果没打开文件直接返回NULL。
                return NULL;
        if(fread(&BMFH,1,sizeof(BMFH),fp)!=sizeof(BMFH))//首先读取BITMAPFILEHEADER结构
                goto ErrHandler;//不能读取完整的结构体返回NULL。

        if(BMFH.bfType!=0x4D42)
                goto ErrHandler;//文件标识不是"BM",返回NULL。

        if(fread(&(BMIF.bmiHeader),1,sizeof(BMIF.bmiHeader),fp)!=sizeof(BMIF.bmiHeader))//然后读取BITMAPINFOHEADER结构
                goto ErrHandler;//不能读取完整的结构体返回NULL。

        if(BMIF.bmiHeader.biSize!=sizeof(BMIF.bmiHeader))//结构体大小必须为40个字节
                goto ErrHandler;//否则文件不合法

        if(BMIF.bmiHeader.biBitCount<=8)//八位以下的位图有调色板数据
        {
                iUsage=DIB_PAL_COLORS;//使用调色板颜色
                uPaletteColors=1<<BMIF.bmiHeader.biBitCount;//调色板颜色数
                if(fread(BMIF.bmiColors,sizeof(RGBQUAD),uPaletteColors,fp)!=uPaletteColors)//读取调色板
                        goto ErrHandler;//不能读取完整的调色板则返回NULL。
        }

        if(!(hBMPRet=CreateDIBSection(hDC,(BITMAPINFO*)&BMIF,iUsage,ppBits,NULL,0)))
                goto ErrHandler;//如果无法创建DIB Section则返回NULL
       
        if(!*ppBits)
                goto ErrHandler;//如果没有得到像素数据的指针返回NULL
       
        if(!BMIF.bmiHeader.biSizeImage)//如果没有读取出位图数据的大小则自动计算位图数据的大小
        {
                UINT uPitch=((BMIF.bmiHeader.biWidth-1)*BMIF.bmiHeader.biBitCount/32+1)*4;//每行字节数
                BMIF.bmiHeader.biSizeImage=uPitch*BMIF.bmiHeader.biHeight;//计算出实际尺寸
        }
       
        if(fread(*ppBits,1,BMIF.bmiHeader.biSizeImage,fp)!=BMIF.bmiHeader.biSizeImage)//读取位图的颜色数据
                goto ErrHandler;//如果读取不了完整的数据则返回NULL
       
        fclose(fp);
        return hBMPRet;
ErrHandler://出错处理
        if(hBMPRet)
                DeleteObject(hBMPRet);
        if(fp)
                fclose(fp);
        return NULL;
}方法2的C语言实现://需要用到的头文件:
#include<stdio.h>
#include<malloc.h>
#include<windows.h>

//定义一个包含256个调色板的结构体,来读取带调色板的BITMAPINFO
typedef struct
{
        BITMAPINFOHEADER        bmiHeader;
        RGBQUAD                                bmiColors;
}BITMAPINFO_;//类似于BITMAPINFO


BITMAPINFO_        g_BMIF;
void                *g_pBits;


//读取BMP的函数
BOOL ReadBMPFile(HDC hDC,char*pszBMPFile)
{
        BITMAPFILEHEADER        BMFH;
        UINT                                uPaletteColors=0;//调色板颜色数
        UINT                                iUsage=DIB_RGB_COLORS;//是否为调色板颜色
        FILE                                *fp=fopen(pszBMPFile,"rb");//打开文件
       
        if(!fp)//如果没打开文件直接返回FALSE。
                return FALSE;
        if(fread(&BMFH,1,sizeof(BMFH),fp)!=sizeof(BMFH))//首先读取BITMAPFILEHEADER结构
                goto ErrHandler;//不能读取完整的结构体返回FALSE。

        if(BMFH.bfType!=0x4D42)
                goto ErrHandler;//文件标识不是"BM",返回FALSE。

        if(fread(&(g_BMIF.bmiHeader),1,sizeof(g_BMIF.bmiHeader),fp)!=sizeof(g_BMIF.bmiHeader))//然后读取BITMAPINFOHEADER结构
                goto ErrHandler;//不能读取完整的结构体返回FALSE。

        if(g_BMIF.bmiHeader.biSize!=sizeof(g_BMIF.bmiHeader))//结构体大小必须为40个字节
                goto ErrHandler;//否则文件不合法

        if(g_BMIF.bmiHeader.biBitCount<=8)//八位以下的位图有调色板数据
        {
                iUsage=DIB_PAL_COLORS;//使用调色板颜色
                uPaletteColors=1<<g_BMIF.bmiHeader.biBitCount;//调色板颜色数
                if(fread(g_BMIF.bmiColors,sizeof(RGBQUAD),uPaletteColors,fp)!=uPaletteColors)//读取调色板
                        goto ErrHandler;//不能读取完整的调色板则返回FALSE。
        }

        if(!g_BMIF.bmiHeader.biSizeImage)//如果没有读取出位图数据的大小则自动计算位图数据的大小
        {
                UINT uPitch=((g_BMIF.bmiHeader.biWidth-1)*g_BMIF.bmiHeader.biBitCount/32+1)*4;//每行字节数
                g_BMIF.bmiHeader.biSizeImage=uPitch*g_BMIF.bmiHeader.biHeight;//计算出实际尺寸
        }

        g_pBits=malloc(g_BMIF.bmiHeader.biSizeImage);//分配内存来读取BMP位图数据
        if(!g_pBits)
                goto ErrHandler;//内存不足,返回
       
        if(fread(g_pBits,1,g_BMIF.bmiHeader.biSizeImage,fp)!=g_BMIF.bmiHeader.biSizeImage)//读取位图的颜色数据
                goto ErrHandler;//如果读取不了完整的数据则返回FALSE
       
        fclose(fp);
        return TRUE;
ErrHandler://出错处理
        if(g_pBits)
        {
                free(g_pBits);
                g_pBits=NULL;
        }
        if(fp)
                fclose(fp);
        return FALSE;
}

/******************************************************************************
读取了BMP文件之后,调用StretchDIBits就能画图。
StretchDIBits函数原型:
int StretchDIBits(
HDC hdc,                      // handle to DC
int XDest,                  // x-coord of destination upper-left corner
int YDest,                  // y-coord of destination upper-left corner
int nDestWidth,               // width of destination rectangle
int nDestHeight,            // height of destination rectangle
int XSrc,                     // x-coord of source upper-left corner
int YSrc,                     // y-coord of source upper-left corner
int nSrcWidth,                // width of source rectangle
int nSrcHeight,               // height of source rectangle
CONST VOID *lpBits,         // bitmap bits
CONST BITMAPINFO *lpBitsInfo, // bitmap data
UINT iUsage,                  // usage options
DWORD dwRop                   // raster operation code
);

函数运行成功的话,返回实际绘制的扫描线数目。
否则返回GDI_ERROR。

调用范例:

StretchDIBits(hDC,X坐标,Y坐标,宽度,高度,
        原图裁剪开始X,原图裁剪开始Y,原图裁剪宽度,原图裁剪高度,
        g_pBits,(BITMAPINFO*)&g_BMIF,
        (g_BMIF.bmiHeader.biBitCount<=8)?DIB_PAL_COLORS:DIB_RGB_COLORS,
        SRCCOPY);

其中的“hDC”是你要绘制的目标的设备句柄,
“X坐标,Y坐标,宽度,高度”是你要绘制到的位置,
“原图裁剪开始X,原图裁剪开始Y,原图裁剪宽度,原图裁剪高度”是你要裁剪的尺寸,
“g_pBits”是刚才读取到的位图数据,
“SRCCOPY”是绘图方式:“直接复制”(详见MSDN的BitBlt文档)
******************************************************************************/

葡萄成熟时 发表于 2014-3-24 23:10:53

fclose(fp);
    return hBMPRet;

ErrHandler://出错处理
    if(hBMPRet)
      DeleteObject(hBMPRet);
    if(fp)
      fclose(fp);
    return NULL;

表示这里看不懂,已经return了,后面的代码还有用?是不是应该先处理错误最后才返回?

0xAA55 发表于 2014-3-25 00:46:36

葡萄成熟时 发表于 2014-3-24 15:10
fclose(fp);
    return hBMPRet;



你没看到ErrHandler:这个标号吗?你不知道goto跳转语句?

葡萄成熟时 发表于 2014-3-25 09:28:57

0xAA55 发表于 2014-3-25 00:46
你没看到ErrHandler:这个标号吗?你不知道goto跳转语句?

嗯嗯,明白了:lol
页: [1]
查看完整版本: 【C】Windows下C语言读取BMP文件并通过GDI显示的方法