雖然現在網上已經有很多位圖讀取、保存的文章,很多寫的都很詳細,提供的源代碼功能也很強大,但是我仍然要自己重寫一個位圖加載程序。主要是因爲這些大牛們的文章寫的太深奧了,代碼功能太強大了,以至於像我這樣的菜鳥讀不懂。所以,我要力求簡潔。省略掉一些細節,比方說調色板。爲了能夠方便容易操作,我的程序只支持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);
- }
- }