雖然現在網上已經有很多位圖讀取、儲存的文章,很多寫的都很詳細,提供的源代碼功能也很強大,但是我仍然要自己重寫一個位圖加載程式。主要是因為這些大牛們的文章寫的太深奧了,代碼功能太強大了,以至于像我這樣的菜鳥讀不懂。是以,我要力求簡潔。省略掉一些細節,比方說調色闆。為了能夠友善容易操作,我的程式隻支援24位以上的位圖檔案加載。
首先,了解下位圖檔案的結構。24位以上的位圖檔案包含3個部分:位圖檔案頭(BITMAPFILEHEADER)、位圖資訊頭(BITMAPINFOHEADER)、位圖資料。
下面是MSDN中兩個資訊頭的具體定義:
typedef struct tagBITMAPFILEHEADER {
WORD bfType; //圖像類型:必須是‘BM’(BM的16進制編碼為:0x4d42)
DWORD bfSize; //位圖檔案大小。
WORD bfReserved1; //保留值。必須為0
WORD bfReserved2; //保留值。必須為0
DWORD bfOffBits; //從檔案開頭到像素資料的偏移量。
} BITMAPFILEHEADER, *PBITMAPFILEHEADER;
typedef struct tagBITMAPINFOHEADER{
DWORD biSize; //目前結構體的大小。
LONG biWidth; //位圖的寬度。機關是像素
LONG biHeight; //位圖高度。機關是像素
WORD biPlanes; //位圖平面個數。必須是1
WORD biBitCount //位圖的位數,也就是位圖深度。可以是1、4、8、16、24、32。16位以下的位圖檔案含有調色闆資訊。
DWORD biCompression; //壓縮方式。位圖沒有壓縮,賦予BI_RGB參數(也就是0)。
DWORD biSizeImage; //位圖資料占用的位元組數。可以設定為預設值0。
LONG biXPelsPerMeter; //指定目标裝置水準分辨率,機關是每米的像素個數。可設定為0
LONG biYPelsPerMeter; //指定目标裝置垂直分辨率,同上。
DWORD biClrUsed; //指定本圖像實際用到的像素。可設定為0
DWORD biClrImportant; //指定本圖像重要的顔色個數。可設定為0,表示所有顔色都重要。
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;
接下來就是位圖資料了:
位圖按行存貯,位圖的寬度不等于位圖的行位元組數。每個像素占用 biBitCount/8 個位元組,且每行占用的位元組數必須是4位元組的整數倍!例如:101*101*24的位圖,行占用位元組數為:101*3=303,而303%4!=0,是以行占用位元組數需修改為304,此時位圖資料的實際大小為304*101,上面的biSizeImage就可設定為304*101。
下面是儲存位圖檔案的結構體填充例:
BITMAPFILEHEADER bmheader;
memset(&bmheader,0,sizeof(bmheader));
bmheader.bfType=0x4d42; //圖像格式。必須為'BM'格式。
bmheader.bfOffBits = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER); //從檔案開頭到資料的偏移量
bmheader.bfSize = m_nWidthBytes*m_nHeight + bmheader.bfOffBits;//檔案大小
BITMAPINFOHEADER bmInfo;
memset(&bmInfo,0,sizeof(bmInfo));
bmInfo.biSize = sizeof(bmInfo);
bmInfo.biWidth = m_nWidth;
bmInfo.biHeight = m_nHeight;
bmInfo.biPlanes = 1;
bmInfo.biBitCount = m_nDeep;
bmInfo.biCompression = BI_RGB;
注:m_nWidthBytes 可這樣計算:int m_nWidthBytes = ((m_nWidth*m_nDeep/8 + 3)/4) * 4;
下面是一個用來建立DIB位圖,獲得HBITMAP句柄一個API。
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
);
(這裡就不詳細介紹了,可以去百科裡看看,如: http://www.hudong.com/wiki/CreateDIBSection)
大緻說下打開檔案檔案的流程:
1.讀入檔案頭BITMAPFILEHEADER資料。
2.讀入位圖資訊頭BITMAPINFOHEADER資料。
3.讀取位圖資料。
4.使用CreateDIBSection建立Dib位圖,将讀取到的資料拷貝給Dib位圖。
然後就可以使用Dib位圖句柄進行位圖的操作了。
儲存檔案流程:
1.填寫檔案頭BITMAPFILEHEADER,并寫入檔案。
2.填寫位圖資訊頭BITMAPINFOHEADER,并寫入檔案。
3.獲得位圖資料(GetBitmapBits,或者是在CreateDIBSection時,将資料位址記錄下來),将資料寫入檔案。
截圖功能(參考樣例見文章末尾):
1.建立掩碼DC(CreateCompatibelDC),掩碼位圖(CreateCompatibleBitmap),将位圖選到DC中。
2.将 要截圖的視窗的DC内容拷貝(BitBlt)給掩碼DC(也就是拷貝到了掩碼位圖上了)。
3.接着就是上面說到的儲存檔案流程了。
附件:我将上面的操作封裝起來。
//DIBBitmap.h
#pragma once
class CDIBBitmap
{
public://方法
CDIBBitmap(void);
~CDIBBitmap(void);
bool create(HDC hdc,int width,int height,int deep);//建立位圖
bool create(HDC hDC,HBITMAP hBitmap);//通過拷貝來建立位圖
bool load(HDC hDC,char *szFileName);//從檔案中價值位圖
bool save(char *szFileName);//儲存位圖到檔案
void clear(void);//清空資料
void sampleRender(HDC hDC,int x,int y);//簡單顯示
public://屬性
//獲得位圖
inline HBITMAP getBitmap(void){ return m_hDibBmp; }
//獲得資料
inline BYTE * getBmpData(void){ return m_pBmpData; }
//獲得寬度
inline int getWidth(void){ return m_nWidth; }
//獲得高度
inline int getHeight(void){ return m_nHeight; }
//獲得位深度
inline int getDeep(void){ return m_nDeep; }
//獲得一行位元組數
inline int getWidthBytes(void){ return m_nWidthBytes; }
protected://成員
HBITMAP m_hDibBmp;
BYTE *m_pBmpData;//指向位圖資料
int m_nWidth; //寬度。(機關:像素pixel)
int m_nHeight; //高度。pixel
int m_nDeep; //深度。bit。深度必須大于24位
int m_nWidthBytes;//一行所需的位元組數。必須是4位元組的整數倍。
};
#include <Windows.h>
#include <stdio.h>
#include <cassert>
#include "DIBBitmap.h"
class FileHoder
{
FILE *m_pFile;
public:
FileHoder(FILE *pFile)
: m_pFile(pFile)
{}
~FileHoder()
{
if (m_pFile) fclose(m_pFile);
}
};
//
CDIBBitmap::CDIBBitmap(void)
{
m_hDibBmp = NULL;
m_pBmpData = NULL;
m_nWidth = 0; //寬度。(機關:像素pixel)
m_nHeight = 0; //高度。pixel
m_nDeep = 0; //深度。bit。深度必須大于24位
m_nWidthBytes = 0;
}
CDIBBitmap::~CDIBBitmap(void)
{
clear();
}
bool CDIBBitmap::create(HDC hdc, int width, int height, int deep)
{
clear();
m_nWidth = width;
m_nHeight = height;
m_nDeep = deep;
m_nWidthBytes = ((m_nWidth*m_nDeep / 8 + 3) / 4) * 4;
m_pBmpData = nullptr;
BITMAPINFO bmInfo;
memset(&bmInfo, 0, sizeof(bmInfo));
bmInfo.bmiHeader.biSize = sizeof(bmInfo);
bmInfo.bmiHeader.biWidth = m_nWidth;
bmInfo.bmiHeader.biHeight = m_nHeight;
bmInfo.bmiHeader.biPlanes = 1;
bmInfo.bmiHeader.biBitCount = m_nDeep;
bmInfo.bmiHeader.biCompression = BI_RGB;
//如果建立成功,m_pBmpData将指向位圖的像素區域。
m_hDibBmp = CreateDIBSection(hdc, &bmInfo, DIB_RGB_COLORS, (void**) &m_pBmpData, NULL, 0);
if (m_hDibBmp == NULL)
{
return false;
}
return true;
}
bool CDIBBitmap::create(HDC hDC, HBITMAP hBitmap)
{
if (NULL == hDC || NULL == hBitmap)
{
return false;
}
//獲得位圖資訊
BITMAP bm;
GetObject(hBitmap, sizeof(bm), &bm);
//建立位圖
if (!create(hDC, bm.bmWidth, bm.bmHeight, bm.bmBitsPixel))
{
return false;
}
//獲得hBitmap資料,并拷貝給Dib位圖
GetBitmapBits(hBitmap, bm.bmWidthBytes*bm.bmHeight, getBmpData());
return true;
}
void CDIBBitmap::clear(void)
{
if (m_hDibBmp != NULL)
{
DeleteObject(m_hDibBmp);
}
m_hDibBmp = NULL;
m_pBmpData = NULL;
}
void CDIBBitmap::sampleRender(HDC hDC, int x, int y)
{
if (!m_hDibBmp) return;
HDC memDC = CreateCompatibleDC(0);
SelectObject(memDC, m_hDibBmp);
BitBlt(hDC, x, y, m_nWidth, m_nHeight, memDC, 0, 0, SRCCOPY);
DeleteDC(memDC);
}
bool CDIBBitmap::load(HDC hDC, char *szFileName)
{
assert(hDC && szFileName && "CDIBBitmap::load");
FILE *fp = NULL;
fopen_s(&fp, szFileName, "rb");
if (NULL == fp)
{
return false;
}
FileHoder holder(fp);
//讀入檔案頭
BITMAPFILEHEADER bmheader;
if (fread(&bmheader, sizeof(bmheader), 1, fp) != 1)
{
return false;
}
//無效的位圖檔案
if (bmheader.bfType != 0x4d42)
{
return false;
}
//讀入位圖資訊頭
BITMAPINFOHEADER bmInfo;
if (fread(&bmInfo, sizeof(bmInfo), 1, fp) != 1)
{
return false;
}
//注:目前程式不支援調色闆資料位圖加載。
if (bmInfo.biBitCount < 16)
{
return false;
}
//建立DIB位圖
if (!create(hDC, bmInfo.biWidth, bmInfo.biHeight, bmInfo.biBitCount))
{
return false;
}
//定位到像素資料
fseek(fp, bmheader.bfOffBits, SEEK_SET);
//讀入像素資料
if (fread(m_pBmpData, m_nWidthBytes, m_nHeight, fp) != m_nHeight)
{
return false;
}
return true;
}
bool CDIBBitmap::save(char *szFileName)
{
assert(szFileName && "CDIBBitmap::save");
if (NULL == m_hDibBmp || NULL == m_pBmpData)
{
return false;
}
FILE *fp = NULL;
fopen_s(&fp, szFileName, "wb");
if (NULL == fp)
{
return false;
}
FileHoder hoder(fp);
BITMAPFILEHEADER bmheader;
memset(&bmheader, 0, sizeof(bmheader));
bmheader.bfType = 0x4d42; //圖像格式。必須為'BM'格式。
bmheader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER); //從檔案開頭到資料的偏移量
bmheader.bfSize = m_nWidthBytes*m_nHeight + bmheader.bfOffBits;//檔案大小
BITMAPINFOHEADER bmInfo;
memset(&bmInfo, 0, sizeof(bmInfo));
bmInfo.biSize = sizeof(bmInfo);
bmInfo.biWidth = m_nWidth;
bmInfo.biHeight = m_nHeight;
bmInfo.biPlanes = 1;
bmInfo.biBitCount = m_nDeep;
bmInfo.biCompression = BI_RGB;
fwrite(&bmheader, sizeof(bmheader), 1, fp);
fwrite(&bmInfo, sizeof(bmInfo), 1, fp);
for (int i = 0; i < m_nHeight; ++i)
{
int index = m_nWidthBytes*i;
fwrite(m_pBmpData + index, m_nWidthBytes, 1, fp);
}
return true;
}
//
截圖功能參考代碼:
//獲得裝置描述表
m_hDeviceContext=GetDC(m_hWnd);
//建立掩碼dc
m_hBackDC=CreateCompatibleDC(m_hDeviceContext);
//建立掩碼位圖
m_bBackBitmap=CreateCompatibleBitmap(m_hDeviceContext,m_nWidth,m_nHeight);
SelectObject(m_hBackDC,m_bBackBitmap);
...
将m_hDeviceContext的内容使用BitBlt拷貝給m_hBackDC
...
CDIBBitmap bmp;
if(!bmp.create(m_hBackDC,m_bBackBitmap))
{
g_log.write("截圖失敗!");
}
else
{
CreateDirectory(TEXT("shot"),NULL);
char buffer[64];
SYSTEMTIME tm;
GetLocalTime(&tm);
sprintf_s(buffer,64,"shot/%d_%d_%d_%d_%d_%d.bmp",
tm.wYear,tm.wMonth,tm.wDay,tm.wHour,tm.wMinute,tm.wSecond);
if(!bmp.save(buffer))
{
g_log.writex("儲存位圖【%s】失敗!",buffer);
}
}