天天看點

Visual C++中DDB與DIB位圖程式設計全攻略(2)

4. DIB位圖程式設計

  4.1位圖檔案格式

  先來分析DIB位圖檔案的格式。位圖檔案分為四部分:

  (1)位圖檔案頭BITMAPFILEHEADER

  位圖檔案頭BITMAPFILEHEADER是一個結構體,長度為14位元組,定義為:

typedef struct tagBITMAPFILEHEADER

{

 WORD bfType; //檔案類型,必須是0x424D,即字元串"BM"

 DWORD bfSize; //檔案大小,包括BITMAPFILEHEADER的14個位元組

 WORD bfReserved1; //保留字

 WORD bfReserved2; //保留字

 DWORD bfOffBits; //從檔案頭到實際的位圖資料的偏移位元組數

} BITMAPFILEHEADER;

  (2)位圖資訊頭BITMAPINFOHEADER

  位圖資訊頭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;

 //指定位圖是否壓縮,有效的值為BI_RGB,BI_RLE8,BI_RLE4,BI_BITFIELDS等,BI_RGB表示不壓縮

 DWORD biSizeImage;

 //實際的位圖資料占用的位元組數,即 biSizeImage=biWidth’ × biHeight,biWidth’是biWidth 按照4的整倍數調整後的結果

 LONG biXPelsPerMeter; //目标裝置的水準分辨率,機關是每米的象素個數

 LONG biYPelsPerMeter; //目标裝置的垂直分辨率,機關是每米的象素個數

 DWORD biClrUsed; //位圖實際用到的顔色數,0表示顔色數為2biBitCount

 DWORD biClrImportant; //位圖中重要的顔色數,0表示所有顔色都重要

} BITMAPINFOHEADER;

  (3)調色闆Palette

  調色闆Palette針對的是需要調色闆的位圖,即單色、16色和256色位圖。對于不以調色闆方式存儲的位圖,則無此項資訊。調色闆是一個數組,共有biClrUsed個元素(如果該值為0,則有2biBitCount個元素)。數組中每個元素是一個RGBQUAD結構體,長度為4個位元組,定義為:

typedef struct tagRGBQUAD

{

 BYTE rgbBlue; //藍色分量

 BYTE rgbGreen; //綠色分量

 BYTE rgbRed; //紅色分量

 BYTE rgbReserved; //保留值

} RGBQUAD;

  (4)實際的位圖資料ImageDate

  對于用到調色闆的位圖,實際的圖象資料ImageDate為該象素的顔色在調色闆中的索引值;對于真彩色圖,圖象資料則為實際的R、G、B值:

  a.單色位圖:用1bit就可以表示象素的顔色索引值;

  b.16色位圖:用4bit可以表示象素的顔色索引值;

  c. 256色位圖:1個位元組表示1個象素的顔色索引值;

  d.真彩色:3個位元組表示1個象素的顔色R,G,B值。

  此外,位圖資料每一行的位元組數必須為4的整倍數,如果不是,則需要補齊。奇怪的是,位圖檔案中的資料是從下到上(而不是從上到下)、從左到右方式存儲的。

4.2位圖的顯示

 

  Visual C++ MFC中沒有提供一個專門的類來處理DIB位圖,是以,為了友善地使用位圖檔案,我們有必要派生一個CDib類。類的源代碼如下:

  (1) CDib類的聲明

// DIB.h:類CDib聲明頭檔案

#ifndef __DIB_H__

#define __DIB_H__

#include <wingdi.h>

class CDib

{

 public:

  CDib();

  ~CDib();

  BOOL Load( const char * );

  BOOL Save( const char * );

  BOOL Draw( CDC *, int nX = 0, int nY = 0, int nWidth = -1, int nHeight = -1, int mode = SRCCOPY);

  BOOL SetPalette( CDC * );

 private:

  CPalette m_Palette;

  unsigned char *m_pDib, *m_pDibBits;

  DWORD m_dwDibSize;

  BITMAPINFOHEADER *m_pBIH;

  RGBQUAD *m_pPalette;

  int m_nPaletteEntries;

};

#endif

  (2) CDib類的實作

// DIB.cpp:類CDib實作檔案

#include "stdafx.h"

#include "DIB.h"

CDib::CDib()

{

 m_pDib = NULL;

}

CDib::~CDib()

{

 // 如果位圖已經被加載,釋放記憶體

 if (m_pDib != NULL)

  delete []m_pDib;

}

  下面這個函數非常重要,其功能為加載位圖,類似于CBitmap類的LoadBitmap函數:

BOOL CDib::Load(const char *pszFilename)

{

 CFile cf;

 // 打開位圖檔案

 if (!cf.Open(pszFilename, CFile::modeRead))

  return (FALSE);

 // 獲得位圖檔案大小,并減去BITMAPFILEHEADER的長度

 DWORD dwDibSize;

 dwDibSize = cf.GetLength() - sizeof(BITMAPFILEHEADER);

 // 為DIB位圖配置設定記憶體

 unsigned char *pDib;

 pDib = new unsigned char[dwDibSize];

 if (pDib == NULL)

  return (FALSE);

 BITMAPFILEHEADER BFH;

 // 讀取位圖檔案資料

 try

 {

  // 檔案格式是否正确有效

  if ( cf.Read(&BFH, sizeof(BITMAPFILEHEADER)) != sizeof(BITMAPFILEHEADER) ||

     BFH.bfType != ’MB’ || cf.Read(pDib, dwDibSize) != dwDibSize)

  {

   delete []pDib;

   return (FALSE);

  }

 }

 catch (CFileException *e)

 {

  e->Delete();

  delete []pDib;

  return (FALSE);

 }

 // delete先前加載的位圖

 if (m_pDib != NULL)

  delete m_pDib;

 // 将臨時Dib資料指針和Dib大小變量賦給類成員變量

 m_pDib = pDib;

 m_dwDibSize = dwDibSize;

 // 為相應類成員變量賦BITMAPINFOHEADER和調色闆指針

 m_pBIH = (BITMAPINFOHEADER*)m_pDib;

 m_pPalette = (RGBQUAD*) &m_pDib[sizeof(BITMAPINFOHEADER)];

 // 計算調色闆中實際顔色數量

 m_nPaletteEntries = 1 << m_pBIH->biBitCount;

 if (m_pBIH->biBitCount > 8)

  m_nPaletteEntries = 0;

 else if (m_pBIH->biClrUsed != 0)

  m_nPaletteEntries = m_pBIH->biClrUsed;

 // 為相應類成員變量賦image data指針

 m_pDibBits = &m_pDib[sizeof(BITMAPINFOHEADER) + m_nPaletteEntries * sizeof (RGBQUAD)];

 // delete先前的調色闆

 if (m_Palette.GetSafeHandle() != NULL)

  m_Palette.DeleteObject();

 // 如果位圖中存在調色闆,建立LOGPALETTE 及CPalette

 if (m_nPaletteEntries != 0)

 {

  LOGPALETTE *pLogPal = (LOGPALETTE*)new char[sizeof(LOGPALETTE) + m_nPaletteEntries *sizeof(PALETTEENTRY)];

  if (pLogPal != NULL)

  {

   pLogPal->palVersion = 0x300;

   pLogPal->palNumEntries = m_nPaletteEntries;

   for (int i = 0; i < m_nPaletteEntries; i++)

   {

    pLogPal->palPalEntry[i].peRed = m_pPalette[i].rgbRed;

    pLogPal->palPalEntry[i].peGreen = m_pPalette[i].rgbGreen;

    pLogPal->palPalEntry[i].peBlue = m_pPalette[i].rgbBlue;

   }

   //建立CPalette并釋放LOGPALETTE的記憶體

   m_Palette.CreatePalette(pLogPal);

   delete []pLogPal;

  }

 }

 return (TRUE);

}

//函數功能:儲存位圖入BMP檔案

BOOL CDib::Save(const char *pszFilename)

{

 if (m_pDib == NULL)

  return (FALSE);

 CFile cf;

 if (!cf.Open(pszFilename, CFile::modeCreate | CFile::modeWrite))

  return (FALSE);

 try

 {

  BITMAPFILEHEADER BFH;

  memset(&BFH, 0, sizeof(BITMAPFILEHEADER));

  BFH.bfType = ’MB’;

  BFH.bfSize = sizeof(BITMAPFILEHEADER) + m_dwDibSize;

  BFH.bfOffBits = sizeof(BITMAPFILEHEADER) +

sizeof(BITMAPINFOHEADER) + m_nPaletteEntries *sizeof(RGBQUAD);

  cf.Write(&BFH, sizeof(BITMAPFILEHEADER));

  cf.Write(m_pDib, m_dwDibSize);

 }

 catch (CFileException *e)

 {

  e->Delete();

  return (FALSE);

 }

 return (TRUE);

}

  下面這個函數也非常重要,其功能為在pDC指向的CDC中繪制位圖,起點坐标為(nX,nY),繪制寬度和高度為nWidth、nHeight,最後一個參數是光栅模式:

BOOL CDib::Draw(CDC *pDC, int nX, int nY, int nWidth, int nHeight, int mode)

{

 if (m_pDib == NULL)

  return (FALSE);

 // 擷取位圖寬度和高度指派

 if (nWidth == - 1)

  nWidth = m_pBIH->biWidth;

 if (nHeight == - 1)

  nHeight = m_pBIH->biHeight;

 // 繪制位圖

 StretchDIBits(pDC->m_hDC, nX, nY, nWidth, nHeight, 0, 0, m_pBIH->biWidth, m_pBIH->biHeight, m_pDibBits, (BITMAPINFO*)m_pBIH, BI_RGB, mode);

 return (TRUE);

}

//函數功能:設定調色闆

BOOL CDib::SetPalette(CDC *pDC)

{

 if (m_pDib == NULL)

  return (FALSE);

 // 檢查目前是否有一個調色闆句柄,對于大于256色的位圖,為NULL

 if (m_Palette.GetSafeHandle() == NULL)

  return (TRUE);

 // 選擇調色闆,接着實施之,最後恢複老的調色闆

 CPalette *pOldPalette;

 pOldPalette = pDC->SelectPalette(&m_Palette, FALSE);

 pDC->RealizePalette();

 pDC->SelectPalette(pOldPalette, FALSE);

 return (TRUE);

}

  從整個CDib類的代碼中我們可以看出,DIB位圖的顯示需遵循如下步驟:

  (1)讀取位圖,本類中使用pDib = new unsigned char[dwDibSize]為位圖中的資訊配置設定記憶體,另一種方法是調用API函數CreateDIBSection,譬如:

m_hBitmap = ::CreateDIBSection(pDC->GetSafeHdc(),

(LPBITMAPINFO) m_lpBMPHdr, DIB_RGB_COLORS,

(LPVOID*) &m_lpDIBits, NULL, 0);

  m_hBitmap定義為:
HBITMAP m_hBitmap;

  (2)根據讀取的位圖資訊,計算出調色闆大小,然後建立調色闆;

  (3)調用CDib::SetPalette( CDC *pDC )設定調色闆,需要用到CDC::SelectPalette及CDC::RealizePalette兩個函數;

  (4)調用CDib::Draw(CDC *pDC, int nX, int nY, int nWidth, int nHeight, int mode)函數繪制位圖。在此函數中,真正發揮顯示位圖作用的是對StretchDIBits API函數的調用。StretchDIBits函數具有縮放功能,其最後一個參數也是光栅操作的模式。

  下面給出DIB位圖的打開及顯示并在其中加入天極網logo的函數源代碼。"DIB位圖"父菜單下"打開"子菜單的單擊事件消息處理函數為(其功能為打開位圖并顯示之):

void CBitMapExampleDlg::OnOpendibpic()

{

 // 彈出檔案對話框,讓使用者選擇位圖檔案

 CFileDialog fileDialog(TRUE, "*.BMP", NULL, NULL,"位圖檔案(*.BMP)|*.bmp;*.BMP|");

 if (IDOK == fileDialog.DoModal())

 {

  // 加載位圖并顯示之

  CDib dib;

  if (dib.Load(fileDialog.GetPathName()))

  {

   CClientDC dc(this);

   dib.SetPalette(&dc);

   dib.Draw(&dc);

  }

 }

}

  "DIB位圖"父菜單下"标記"子菜單的單擊事件消息處理函數為(其功能為給位圖加上天極網logo):

void CBitMapExampleDlg::OnMarkDibpic()

{

 // 彈出檔案對話框,讓使用者選擇标記logo

 CFileDialog fileDialog(TRUE, "*.BMP", NULL, NULL, "标記位圖檔案(*.BMP)|*.bmp;*.BMP|");

 if (IDOK == fileDialog.DoModal())

 {

  // 加載标記logo位圖并與目标位圖相與

  CDib dib;

  if (dib.Load(fileDialog.GetPathName()))

  {

   CClientDC dc(this);

   dib.SetPalette(&dc);

   dib.Draw(&dc, 0, 0, - 1, - 1, SRCAND);

  }

 }

}

  圖4顯示了DIB位圖加載天極網logo後的效果,要好于圖3中加天極網logo後的DDB位圖。圖4顯示的是真彩色位圖互相與的結果,而圖3中的圖像顔色被減少了。
Visual C++中DDB與DIB位圖程式設計全攻略(2)
圖4 在DIB位圖中加入天極網logo

   5. 結束語

  本文介紹了位圖及調色闆的概念,并講解了DDB位圖與DIB位圖的差別。在此基礎上,本文以執行個體講解了DDB位圖和DIB位圖的操作方式。DDB位圖的處理相對比較簡單,對于DIB位圖,我們需要定義一個MFC所沒有的新類CDib,它屏蔽位圖資訊的讀取及調色闆建立的技術細節,應用程式可以友善地使用之。

  本文中的所有程式在Visual C++6.0及Windows XP平台上調試通過。

繼續閱讀