本文主要是講述《數字圖像處理》系列欄目中的第一篇文章.主要詳細介紹了BMP圖片格式,同時使用C++和MFC顯示BMP格式,主要結合自己的《數字圖像處理》課程和以前的項目敘述講解.
一.BMP圖片格式定義
BMP文件格式是Windows操作系統推薦和支持的標準圖像文件格式,是一種將內存或顯示器的圖像數據不經過壓縮而直接按位存盤的文件格式,故稱位圖(bitmap),其擴展名爲BMP.BMP圖像通常有4個部分組成:位圖文件頭、位圖信息頭、顏色表、位圖數據.如下圖所示:
第一部分爲位圖文件頭BITMAPFILEHEADER.位圖文件頭結構長度固定爲14個字節,包含文件的類型、大小、位圖文件保留字、位圖數據距文件頭的偏移量.其中WORD爲無符號16位整數(2byte),DWORD爲無符號32位整數(4byte).具體結構體定義如下:
-
//位圖文件頭
-
typedef struct tagBITMAPFILEHEADER {
-
WORD bfType; //位圖文件的類型,必須爲BM 0x424d 表示.bmp
-
DWORD bfSize; //位圖文件的大小,以字節爲單位 包括該14字節
-
WORD bfReserved1; //位圖文件保留字,必須爲0
-
WORD bfReserved2; //位圖文件保留字,必須爲0
-
DWORD bfOffBits; //位圖數據距文件頭的偏移量,以字節爲單位 即前三部分和
-
} BITMAPFILEHEADER;
第二部分爲位圖信息頭BITMAPINFOHEADER,該結構也固定爲40個字節,用於說明位圖的尺寸、寬高、像素、分辨率、顏色表等信息.具體結構定義如下:
-
//位圖信息頭
-
typedef struct tagBITMAPINFOHEADER {
-
DWORD biSize; //本結構所佔用字節數 40字節
-
LONG biWidth; //位圖的寬度,以像素爲單位
-
LONG biHeight; //位圖的高度,以像素爲單位
-
WORD biPlanes; //目標設備的級別,必須爲1
-
WORD biBitCount; //每個像素所需的位數,必須是1(雙色)、
-
//4(16色)、8(256色)或24(真彩色)之一
-
DWORD biCompression; //位圖壓縮類型,必須是 0(BI_RGB不壓縮)、
-
//1(BI_RLE8壓縮類型)或2(BI_RLE壓縮類型)之一
-
DWORD biSizeImage; //位圖的大小,以字節爲單位
-
LONG biXPelsPerMeter; //位圖水平分辨率,每米像素數
-
LONG biYPelsPerMeter; //位圖垂直分辨率,每米像素數
-
DWORD biClrUsed; //位圖實際使用的顏色表中的顏色數
-
DWORD biClrImportant; //位圖顯示過程中重要的顏色數
-
} BITMAPINFOHEADER;
第三部分爲顏色表或調色板(Palette).有些位圖需要調色板,有些位圖如真彩色圖(biBitCount=24)不需要調色板,它們的BITMAPINFOHEADER後面直接是位圖數據.調色板實際是一個數組,共有biClrUsed個元素(如果該值爲零,則有2的biBitCount次冪個元素).數組中每個元素的類型是一個RGBQUAD結構,佔4字節.定義如下:
-
//位圖顏色表
-
typedef struct tagRGBQUAD
-
{
-
BYTE rgbBlue; //藍色的亮度(值範圍爲0~255)
-
BYTE rgbGreen; //綠色的亮度(值範圍爲0~255)
-
BYTE rgbRed; //紅色的亮度(值範圍爲0~255)
-
BYTE rgbReserved; //保留,必須爲0
-
} RGBQUAD;
第四部分就是實際的圖像數據.對於真彩色圖(24位位圖 biBitCount=24),圖像數據就是實際的RGB值;對於用到調色板的位圖,圖像數據就是該像素顏色在調色板中的索引值.下面對2色、16色、256色和真彩色位圖分別介紹:
(1).2色位圖:當biBitCount=1時,用1位就可以表示該像素的顏色(0表示黑,1表示白),所以8個像素佔1個字節;
(2).16色位圖:當biBitCount=4時,用4爲可以表示一個像素的顏色,所以2個像素佔1個字節;
(3).256色位圖:當biBitCount=8時,用1個字節表示1個像素,1個像素佔1個字節;
(4).真彩色圖:當biBitCount=24時,此時用3個字節表示1個像素,其中RGB各佔1字節,由於沒有顏色表,位圖信息頭後面是位圖數據.
同時,注意以下幾點:
1.由於Windows規定一個掃描所佔的字節數必須是4的倍數(即以long爲單位),不足的以0填充.同時注意下面公式,計算只含位圖數據的大小:biSizeImage=(((bi.biWidth*bi.biBitCount)+31)/(32*4))*bi.Height
在後面講述獲取文件的信息時會通過UE軟件結合16進制數據進行詳細講解上面各個數據的具體含義.
2.BMP圖片格式的數據是從下到上、從左到右讀.即文件中最先讀到的圖像是最下面一行的左邊第一個元素,即從左下角開始存儲(0,0)點,從左下角到右上角存儲數據.尤其是在圖像幾何變換平移、旋轉時,我就犯過這樣的錯誤,本想讓圖像從左下角向右上移動,結果剛好相反,後面也會通過實例加深大家的印象.
3.如果想使用C語言\C++顯示圖片,建議自定義個ImageStruct.h的頭文件.包含BMP位圖的位圖文件頭結構、位圖信息頭結構、位圖顏色表3個結構,在實例變量操作.而使用MFC,因爲在wingdi.h文件中系統已經定義了BMP圖像的結構BITMAPFILEHEADER、BITMAPINFOHEADER,直接在View.h中用他倆實例定義即可.
二.顯示BMP圖片的基本步驟
在MFC工程XXXView.h類中添加成員函數void ShowBitmap(CDC* pDC,CString BmpName);通過自定義函數實現顯示BMP格式圖像,其中*pDC是CDC句柄,BmpName是圖像文件名.具體步驟如下:
1.創建位圖並調用函數LoadImage裝載圖標、光標或位圖.
HBITMAP m_hBitmap;
m_hBitmap=(HBITMAP)LoadImage(HINSTANCE hinst,LPCTSTR lpszName,UINT uType,int cxDesired,int cyDesired,UINT fuLoad)
2.定義並創建一個內存設備環境DC,調用函數CreateCompatibleDC創建兼容的DC.
CDC dcBmp; dcBmp.CreateCompatibleDC(pDC) ;
3.定義BITMAP變量,調用函數GetBitmap將圖片載入位圖中,該定義是爲後去圖像的長寬等信息.
BITMAP m_bmp; m_bitmap.GetBitmap(&m_bmp);
4.調用函數SelectObject將位圖選入兼容內存設備環境DC中.
dcBmp.SelectObject(&m_bitmap);
5.將兼容的DC中的位圖填到當前DC中,調用函數BitBlt或strechBlt顯示圖像.
(1).BitBlt()該函數對指定的源設備環境區域中的像素進行位塊(bit_block)轉換,以傳送到目標設備環境.
pDC->BitBlt(0,0,m_bmp.bmWidth,m_bmp.bmHeight,&dcBmp,0,0,SRCCOPY);
(2).stretchBlt()該函數從源矩形中複製位圖到目標矩形,必要是按目標設備設置的模式進行圖像拉伸或壓縮.
pDC->StretchBlt(0,0,m_nDrawWidth,m_nDrawHeight,&dcBmp,0,0,m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY);
6.恢復臨時DC的位圖,刪除CreateCompatibleDC得到的圖片DC,刪除內存中的位圖及釋放系統資源.
dcBmp.SelectObject(pbmpOld); DeleteObject(&m_bitmap); dcBmp.DeleteDC();
具體函數代碼如下:
-
//****************顯示BMP格式圖片****************//
-
void CShowBMPView::ShowBitmap(CDC *pDC, CString BmpName)
-
{
-
//定義bitmap指針 調用函數LoadImage裝載位圖
-
HBITMAP m_hBitmap;
-
m_hBitmap = (HBITMAP) LoadImage(NULL,BmpName,IMAGE_BITMAP,0,0,LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_CREATEDIBSECTION);
-
/*************************************************************************/
-
/* 1.要裝載OEM圖像,則設此參數值爲0 OBM_ OEM位圖 OIC_OEM圖標 OCR_OEM光標
-
/* 2.BmpName要裝載圖片的文件名
-
/* 3.裝載圖像類型:
-
/* IMAGE_BITMAP-裝載位圖 IMAGE_CURSOR-裝載光標 IMAGE_ICON-裝載圖標
-
/* 4.指定圖標或光標的像素寬度和長度 以像素爲單位
-
/* 5.加載選項:
-
/* IR_LOADFROMFILE-指明由lpszName指定文件中加載圖像
-
/* IR_DEFAULTSIZE-指明使用圖像默認大小
-
/* LR_CREATEDIBSECTION-當uType參數爲IMAGE_BITMAP時,創建一個DIB項
-
/**************************************************************************/
-
if( m_bitmap.m_hObject )
-
{
-
m_bitmap.Detach(); //切斷CWnd和窗口聯繫
-
}
-
m_bitmap.Attach(m_hBitmap); //將句柄HBITMAP m_hBitmap與CBitmap m_bitmap關聯
-
//邊界
-
CRect rect;
-
GetClientRect(&rect);
-
//圖片顯示(x,y)起始座標
-
int m_showX=0;
-
int m_showY=0;
-
int m_nWindowWidth = rect.right - rect.left; //計算客戶區寬度
-
int m_nWindowHeight = rect.bottom - rect.top; //計算客戶區高度
-
//定義並創建一個內存設備環境DC
-
CDC dcBmp;
-
if( !dcBmp.CreateCompatibleDC(pDC) ) //創建兼容性的DC
-
return;
-
BITMAP m_bmp; //臨時bmp圖片變量
-
m_bitmap.GetBitmap(&m_bmp); //將圖片載入位圖中
-
CBitmap *pbmpOld = NULL;
-
dcBmp.SelectObject(&m_bitmap); //將位圖選入臨時內存設備環境
-
//圖片顯示調用函數stretchBlt
-
pDC->StretchBlt(0,0,m_bmp.bmWidth,m_bmp.bmHeight,&dcBmp,0,0,m_bmp.bmWidth,m_bmp.bmHeight,SRCCOPY);
-
/*******************************************************************************/
-
/* BOOL StretchBlt(int x,int y,int nWidth,int nHeight,CDC* pSrcDC,
-
/* int xSrc,int ySrc,int nSrcWidth,int nSrcHeight,DWORD dwRop );
-
/* 1.參數x、y位圖目標矩形左上角x、y的座標值
-
/* 2.nWidth、nHeigth位圖目標矩形的邏輯寬度和高度
-
/* 3.pSrcDC表示源設備CDC指針
-
/* 4.xSrc、ySrc表示位圖源矩形的左上角的x、y邏輯座標值
-
/* 5.dwRop表示顯示位圖的光柵操作方式 SRCCOPY用於直接將位圖複製到目標環境中
-
/*******************************************************************************/
-
dcBmp.SelectObject(pbmpOld); //恢復臨時DC的位圖
-
DeleteObject(&m_bitmap); //刪除內存中的位圖
-
dcBmp.DeleteDC(); //刪除CreateCompatibleDC得到的圖片DC
-
}
補充:MFC中DC指device context,設備環境或設備描述表,它其實是GDI內部保存數據的一種數據結構.此結構中屬性內容與特定輸出設備相關,屬性定義了GDI函數的工作細節.總之,使用GDI繪圖函數,就需要一個DC句柄,MFC中把和DC相關的封裝成類.其中CDC是一個抽象基類,可以訪問整個顯示器和打印機.其中下面鏈接中的文章詳細描述了CBitmap、HBitmap、Bitmap三者的區別與聯繫.http://blog.csdn.net/ivan_ljf/article/details/8569130
三.MFC顯示BMP圖片
下面將詳細講解使用VS2012 MFC創建工程的具體步驟:
第一步:新建項目"MFC應用程序",項目名爲ShowBMP,在應用程序類型中選擇"單個文檔",點擊"確定".在右欄的"資源視圖"中,點擊"Menu->IDR_MAINFRAM"可以查看並修改菜單視圖.
第二步:向CShowBMPView類添加成員變量和成員函數.在右欄的"類視圖"右鍵CShowBMPView添加函數或直接在ShowBMPView.h中直接添加public成員變量和成員函數.添加代碼如下:
-
public:
-
//成員變量
-
CString BmpName; //保存圖像文件文件名
-
CString EntName; //保存圖像文件擴展名
-
CBitmap m_bitmap; //創建位圖對象
-
//成員函數
-
void ShowBitmap(CDC* pDC,CString BmpName); //用來顯示指定位圖bmp的函數
第三步:設置打開BMP圖片函數."項目"->"類嚮導"->選擇"類名"CShowBMPView->在命令對象ID中雙擊"ID_FILE_OPEN"->自動生成默認成員函數OnFileOpen,消息爲COMMAND.雙擊成員函數(Member Functions)進入函數編輯.(VC++ 6.0中Ctrl+W可以實現建立類嚮導)
向添加成員函數CShowBMPView::OnFileOpen()添加如下代碼,主要是生成打開圖片的對話框,並獲取圖片路徑及後綴.自定義四種格式爲bmp gif jpg tiff,但目前只能打開bmp格式圖片.
-
//**************文件打開****************//
-
void CShowBMPView::OnFileOpen()
-
{
-
//四種格式的文件:bmp gif jpg tiff
-
CString filter;
-
filter="所有文件(*.bmp,*.jpg,*.gif,*tiff)|*.bmp;*.jpg;*.gif;*.tiff| BMP(*.bmp)|*.bmp| JPG(*.jpg)|*.jpg| GIF(*.gif)|*.gif| TIFF(*.tiff)|*.tiff||";
-
CFileDialog dlg(TRUE,NULL,NULL,OFN_HIDEREADONLY,filter,NULL);
-
//按下確定按鈕 dlg.DoModal() 函數顯示對話框
-
if( dlg.DoModal() == IDOK )
-
{
-
BmpName = dlg.GetPathName(); //獲取文件路徑名 如D:\pic\abc.bmp
-
EntName = dlg.GetFileExt(); //獲取文件擴展名
-
EntName.MakeLower(); //將文件擴展名轉換爲一個小寫字符
-
Invalidate(); //調用該函數就會調用OnDraw重繪畫圖
-
}
-
}
第四步:在ShowBMPView.cpp中編寫void CShowBMPView::ShowBitmap(CDC *pDC, CString BmpName)函數,即“二.顯示BMP圖片基本步驟”.同時通過OnDraw()函數調用ShowBitmap()函數顯示圖片.代碼如下:
-
//在OnDraw函數中調用ShowBitmap()實現圖片的顯示功能
-
void CShowBMPView::OnDraw(CDC* pDC)
-
{
-
CShowBMPDoc* pDoc = GetDocument();
-
ASSERT_VALID(pDoc);
-
if (!pDoc)
-
return;
-
// TODO: 在此處爲本機數據添加繪製代碼
-
if( EntName.Compare(_T("bmp")) == 0 ) //bmp格式
-
{
-
ShowBitmap(pDC,BmpName); //顯示圖片
-
}
-
}
四.運行結果及總結
運行程序後,顯示如下所示:其中可以看到自定義的打開對話框和顯示圖片.
最後,該文章主要是數字圖像處理的基礎知識,詳細介紹了BMP圖片格式和使用MFC如何讀取BMP圖片的相關知識.
//////////////////////////////////////////////////////////////////