0%

内存映射-打开BMP图片

读取BMP图片

之前说到LoadBitmap这个函数用来加载一幅位图,现在正好复习到内存映射,就用内存映射来写这么一个相关的操作(这个我是照着书上写的,但是不知道那本书有点老还是QQ截图有问题,有地方我纠结了好一阵,后面说)。

首先我们想要原生态的打开BMP,也就是通过文件映射的方法打开BMP文件,那么我们至少要了解BMP文件的一个文件格式,这个文件格式和PE文件的很像,但是没有那个复杂,无非就是一些图片大小之类的一些信息。
我们要了解的结构体:

  1. BITMAPFILEHEADERBMP图片头文件

    1
    2
    3
    4
    5
    6
    7
    typedef struct tagBITMAPFILEHEADER { // bmfh 
    WORD bfType;
    DWORD bfSize;
    WORD bfReserved1;
    WORD bfReserved2;
    DWORD bfOffBits;
    } BITMAPFILEHEADER;

    结构体比较简单,比较重要的就是第一个和最后一个,第一个就是表示这个文件的一个类型,其实就和PE文件基本上一模一样,PE是MZ,BMP是BM,就是一个模子刻出来的。最后一个就是说真实的图片数据的RVA。

  2. BITMAPINFOBMP 图片信息

    1
    2
    3
    4
    typedef struct tagBITMAPINFO { // bmi 
    BITMAPINFOHEADER bmiHeader;
    RGBQUAD bmiColors[1];
    } BITMAPINFO;

    这个的话呢里面比较重要的就是bmiHeader这个,里面还有一个结构体,这个结构体就是我们的信息了:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    typedef struct tagBITMAPINFOHEADER{ // bmih 
    DWORD biSize;
    LONG biWidth;
    LONG biHeight;
    WORD biPlanes;
    WORD biBitCount
    DWORD biCompression;
    DWORD biSizeImage;
    LONG biXPelsPerMeter;
    LONG biYPelsPerMeter;
    DWORD biClrUsed;
    DWORD biClrImportant;
    } BITMAPINFOHEADER;

    这些信息就比较通俗易懂了,看名字就能看懂了。

程序编写

首先在我们的程序窗口OnCreate的时候我们我们先去创建一个兼容的DC。

1
2
3
4
5
6
7
8
9
10
11
12
int CStudy_BMPDlg::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
if (CDialogEx::OnCreate(lpCreateStruct) == -1)
return -1;
CClientDC dc(this);
m_hMemDC = CreateCompatibleDC(dc);
m_nHeight = 0;
m_nWidth = 0;
// TODO: 在此添加您专用的创建代码

return 0;
}

然后我们将图片的宽高都设置为0。
对了,还是先看看我是怎么声明一些的变量的得吧:

1
2
3
4
private:
HDC m_hMemDC;
int m_nHeight;
int m_nWidth;

这个的话呢,一个是存放我们客户去大小的兼容DC,一个是BMP图片的宽和高。
创建写好之后,我们在销毁的时候不要忘记删除掉我们的DC对象,但是不是普通的delete而是用DeleteDC这个函数进行删除。

1
2
3
4
5
void CStudy_BMPDlg::OnDestroy()
{
CDialogEx::OnDestroy();
DeleteDC(m_hMemDC);
}

这个点击完毕之后,我们可以加一个按钮或者说加一个菜单,这里我为了简单,我就加了一个按钮,但效果的话呢没有加菜单好一些。

按钮点击:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
void CStudy_BMPDlg::OnBnClickedButton1()
{
CFileDialog file(TRUE);
if (!file.DoModal())
return ;
HANDLE hFile = CreateFile(file.GetPathName(),GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if (hFile == INVALID_HANDLE_VALUE)
{
MessageBox(file.GetPathName()+":File Open Fail");
return ;
}
HANDLE hMap = CreateFileMapping(hFile,NULL,PAGE_READONLY,NULL,NULL,NULL);
LPVOID lpBase = MapViewOfFile(hMap,FILE_MAP_READ,0,0,0);
BITMAPFILEHEADER *pFileHeader = (BITMAPFILEHEADER *)lpBase;
if (pFileHeader->bfType != MAKEWORD('B','M'))//MAKEWORD这个宏看名字就能看出来,就是把一个放在高位,一个放在低位形成一个WORD类型的数据,这句话就是判断是不是BMP文件
{
MessageBox("打开BMP文件啊老哥!");
UnmapViewOfFile(lpBase);
CloseHandle(hMap);
CloseHandle(hFile);
return ;
}
BYTE *pBits = (BYTE*)lpBase+pFileHeader->bfOffBits;
BITMAPINFO *pInfoHeader = (BITMAPINFO *)((BYTE*)lpBase+sizeof(BITMAPFILEHEADER));
m_nHeight = abs(pInfoHeader->bmiHeader.biHeight);
m_nWidth = abs(pInfoHeader->bmiHeader.biWidth);
CClientDC dc(this);
HBITMAP hBitmap = CreateCompatibleBitmap(dc,m_nWidth,m_nHeight);
if(SelectObject(m_hMemDC,hBitmap) == NULL)
{
UnmapViewOfFile(lpBase);
CloseHandle(hMap);
CloseHandle(hFile);
MessageBox("选择对象失败");
return;
}
int nRet = SetDIBitsToDevice(m_hMemDC,0,0,m_nWidth,m_nHeight,0,0,0,m_nHeight,pBits,pInfoHeader,DIB_RGB_COLORS);
if (!nRet)
MessageBox("映射失败");
InvalidateRect(NULL,TRUE);
UnmapViewOfFile(lpBase);
CloseHandle(hMap);
CloseHandle(hFile);
}

这里我们就需要说一下,首先我们先用通用对话框打开一个文件,但是不知道是我电脑还是什么情况,书上写的是GetFileName,但是这个的话呢只是获取文件的名字啊,不在一个目录下的话呢相对路径是找不到的,所以我就用了GetPathName这个函数,然后我们就用CreateFile打开一个文件,常规的打开属性就可以了,打开之后我们就开始我们的正题了,就是进行文件的内存映射操作,首先可定时创建一个文件内存映射,由于我们不需要进行内存的共享,所以最后一个参数可以设置为NULL,然后将映射的这块进行分配内存,分配完毕之后我们将这块内存给我们之前说的第一个BMP结构体,这个时候我们就需要用bfType判断一下是不是开头BM了,这个我在注释上详细说了,大家看注释就好了,然后如果不是BMP文件我们就关闭打开的东西,确定是BMP文件之后,我们给一个BYTE的指针,主要是用来保存我们的真是图片数据的,这个图片数据是基地址+相对偏移进行计算出来的,然后我们计算我们的文件信息的地址,这个地址比较有意思,就是说我们第一个结构体紧随其后的就是他,我们只需要用基地址加上第一个BMP结构体的大小就可以计算出来他的一个内存地址了,计算出来之后,在我们获取宽和高的时候问题就来了,在用QQ进行截图生成的BMP文件的时候我在测试过程中,一直出错,不知道原因,慢慢多测试了几次,往上回溯了几次,发现为什么我的高是个负数?我现在也没明白,所以不得不我加上了一个取绝对值的函数,这样就就算可以了,之后我们在创建一个兼容的位图,用来存储我们的图片数据,和我们的客户区DC相兼容,然后将获取到的BMP句柄选入DC,之后我们需要将数据进行拷贝,用到了SetDIBitsToDevice这个函数,就是将数据传输给我们的设备对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int SetDIBitsToDevice(
HDC hdc, // handle to device context
int XDest, // x-coordinate of upper-left corner of
// dest. rect.
int YDest, // y-coordinate of upper-left corner of
// dest. rect.
DWORD dwWidth, // source rectangle width
DWORD dwHeight, // source rectangle height
int XSrc, // x-coordinate of lower-left corner of
// source rect.
int YSrc, // y-coordinate of lower-left corner of
// source rect.
UINT uStartScan, // first scan line in array
UINT cScanLines, // number of scan lines
CONST VOID *lpvBits, // address of array with DIB bits
CONST BITMAPINFO *lpbmi, // address of structure with bitmap info.
UINT fuColorUse // RGB or palette indexes
);

很好理解,倒数第二个参数就是我们的BMP结构信息,最后一个参数:

Value Meaning
DIB_PAL_COLORS The color table consists of an array of 16-bit indexes into the currently selected logical palette.
DIB_RGB_COLORS The color table contains literal RGB values

我们就选择RGB,之后我们就需要关闭各种东西啦,然后使客户区无效。
然后在OnPaint中编写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void CStudy_BMPDlg::OnPaint()
{
CPaintDC dc(this);
BitBlt(dc,0,0,m_nWidth,m_nHeight,m_hMemDC,0,0,SRCCOPY);
if (IsIconic())
{
// 用于绘制的设备上下文

SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

// 使图标在工作区矩形中居中
int cxIcon = GetSystemMetrics(SM_CXICON);
int cyIcon = GetSystemMetrics(SM_CYICON);
CRect rect;
GetClientRect(&rect);
int x = (rect.Width() - cxIcon + 1) / 2;
int y = (rect.Height() - cyIcon + 1) / 2;

// 绘制图标
dc.DrawIcon(x, y, m_hIcon);
}
else
{
CDialogEx::OnPaint();
}

}

不要忘记一点就好了要写在外面,判断不一定进去的,这样我们就写好了我们的BMP浏览。
BitBlt就是一个数据拷贝的函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
BOOL BitBlt(
HDC hdcDest, // handle to destination device context
int nXDest, // x-coordinate of destination rectangle's upper-left
// corner
int nYDest, // y-coordinate of destination rectangle's upper-left
// corner
int nWidth, // width of destination rectangle
int nHeight, // height of destination rectangle
HDC hdcSrc, // handle to source device context
int nXSrc, // x-coordinate of source rectangle's upper-left
// corner
int nYSrc, // y-coordinate of source rectangle's upper-left
// corner
DWORD dwRop // raster operation code
);