最近在做圖像採集的工作,需要處理圖像數據,所以學習了一下位圖顯示,而且只看了設備相關位圖DDB。基本上實現了位圖的顯示、位圖數據的處理等功能。這裏就記錄一下我自己的理解,不一定全都對,僅供參考而已。
要顯示位圖,需要做如下工作:
CStatic* pStatic=(CStatic*)GetDlgItem(IDC_DISPLAY2);
CDC* pDC=pStatic->GetDC();
CBitmap bitmap;
bitmap.LoadBitmap(IDB_BITMAP2);
BITMAP bmp;
bitmap.GetBitmap(&bmp);
CDC dcCompatible;
dcCompatible.CreateCompatibleDC(pDC);
dcCompatible.SelectObject(&bitmap);
CRect rect;
pStatic->GetClientRect(&rect);
pDC->StretchBlt(0,0,rect.Width(),rect.Height(), &dcCompatible,0,0,bmp.bmWidth,bmp.bmHeight,SRCCOPY);
程序中,創建了一個位圖對象bitmap,bitmap.LoadBitmap(IDB_BITMAP2);將一幅位圖加載到了bitmap這個對象中,按我的理解,這個對象是存在在內存中的,所以我們對這個對象的操作並不會對原來那幅位圖有什麼影響。另外,還定義了一個BITMAP結構:bmp。之所以定義這個結構,是爲了獲得位圖的寬度、高度等圖像信息,如果我們知道加載的位圖的高度、寬度、一個像素佔多少字節等信息,那麼我們無需定義這個bmp結構,不過爲了方便,還是定義一個的好。要顯示位圖,還要創建與當前DC兼容的DC,當前DC怎麼獲得?就是下面這兩句:
CStatic* pStatic=(CStatic*)GetDlgItem(IDC_DISPLAY2);
CDC* pDC=pStatic->GetDC();
我要將位圖在對話框中的靜態文本控件中顯示,因此定義了一個指向靜態CStatic對象的指針,這樣,當前DC就需要使用pStatic->GetDC();來獲取。然後創建一個與當前DC兼容的DC: dcCompatible.CreateCompatibleDC(pDC);。這個DC創建了之後,就把內存中的位圖對象bitmap選入這個DC:dcCompatible.SelectObject(&bitmap);,從而確定這個兼容DC的顯示錶面的大小。這裏要搞清楚一個關係。做了以上各步驟之後,其實與那個位圖對象已經沒多少關係了,bitmap這個位圖對象的相關信息已經在dcCompatible這個保存着了,而要顯示的目標區域則由當前DC:pDC所指示出來,這也就是爲什麼要創建兼容DC,只有兩個DC兼容,才能順利的把圖像從dcCompatible複製到pDC中進行顯示。我們看到最後的那句
pDC->StretchBlt(0,0,rect.Width(),rect.Height(), &dcCompatible,0,0,bmp.bmWidth,bmp.bmHeight,SRCCOPY);
實際上就是起到將dcCompatible中的圖像在pDC的區域上顯示的作用。這句話中還有有一個參數rect,它是一個CRect對象,用來得到客戶區的大小,也就是我們所要顯示的區域的大小,在這裏也就是靜態文本框,一個矩形的區域,所以調用pStatic的成員函數GetClientRect()來獲取客戶區域。
這樣,一幅位圖就顯示了出來。上述這段程序是對話框中的一個按鈕的響應函數。我之前看到有人說,要把畫圖程序放在OnDraw或者OnPaint之類的函數中去。我覺得其實放在哪裏都無所謂,只要知道了要顯示位圖需要計算機做什麼工作,就能順利將位圖顯示出來。
學會顯示位圖之後,就得準備處理位圖。做圖像採集的時候,獲得的是一個指向內存中一塊區域的指針pData,這塊區域中存儲着圖像的灰度值。而如果要處理加載的位圖,也需要獲取其位圖數據的指針。因此兩者的處理方法是差不多的。這裏就以處理加載的位圖爲例進行說明。
要獲取位圖數據的指針,可以使用GetBitmapBits(),而要將處理後的數據COPY到位圖對象中,則使用SetBitmapBits(),具體的用法可以查閱MSDN。我加載的位圖是256色的,它的一個像素數據佔4個字節,前三個字節分別表示RGB,第四個字節爲保留字節。因此,如果這幅位圖寬度是1000個像素,那麼其字節寬度就是4000個字節。一般的圖像處理都是針對灰度圖像,也就是說一個像素的RGB值是相等的,從黑色(0,0,0)到白色(255,255,255)變化。因此,我處理圖像的時候,只需要處理它的像素數據的代表R的字節,然後代表G和B的字節就都等於R的值就可以了。下面是代碼:
BYTE *pmydata; //定義一個指針用來指向位圖圖像數據在內存中的存儲區域
pmydata=new BYTE[bmp.bmWidthBytes*bmp.bmHeight]; //根據位圖的高度寬度初始化一下
bitmap.GetBitmapBits(bmp.bmWidthBytes*bmp.bmHeight,pmydata); //將位圖對象的數據COPY到pmydata指向的區域,bitmap是位圖對象,bmp是位圖結構,可參考上一篇文章的定義
for(int i=0;i<bmp.bmWidthBytes*bmp.bmHeight;i+=4) //這裏只是將RGB三個字節的值取平均值,再取個反
{ //因爲一個像素有4個字節,故i+=4
BYTE temp=0;
temp=(pmydata[i]+pmydata[i+1]+pmydata[i+2])/3;
pmydata[i]=255-temp;//R值
pmydata[i+1]=255-temp;//G值
pmydata[i+2]=255-temp;//B值
}
這樣就實現了圖像數據的簡單處理,然後將處理的數據COPY回位圖對象:
bitmap.SetBitmapBits(bmp.bmWidthBytes*bmp.bmHeight,pmydata); //將處理後的數據COPY進位圖對象
隨後按照上一篇文章的步驟顯示位圖即可。
另外要說一下,對於圖像採集上來的灰度值數據,一個像素只有一個字節,而上面顯示的圖像的像素數據則是4個字節,這應該怎麼來轉換一下呢?我用的方法是定義一個COLORREF數組:
COLORREF* m_ColorData=new COLORREF[width*height];
這個COLORREF實際上就是DWORD,上面這句話定義了一個指向一塊存放DWORD類型的數據的指針,也就是說,m_ColorData[i]與m_ColorData[i+1]之間有4個字節的距離,這正好與256色圖像的像素數據存放方式相對應。將灰度值數據處理好之後,轉化成RGB值:
//將灰度值轉換爲RGB值進行顯示
for(i=0;i<width*height;i++)
m_ColorData[i]=RGB( m_bytes[i], m_bytes[i], m_bytes[i]);
其中的m_bytes[i]就是灰度值,上面這句話的意思是將m_ColorData[i]的四個字節中的前三個字節賦值爲:m_bytes[i], m_bytes[i], m_bytes[i]。然後建立一個位圖對象bitmap,使用SetBitmapBits()函數設置它的圖像數據的值。
bitmap.SetBitmapBits(width*height*sizeof(COLORREF),m_ColorData);。然後按照前面說的步驟將這個位圖對象顯示處理即可。