目錄
- 一、CDC類(二級緩存)
- 二、計算視口
- 三、放大操作
- 代碼中初始化操作(方便以后cv)
一、CDC類(二級緩存)
CDC類是設備上下文的核心類,它的作用是抽象化對圖形輸出設備(像屏幕、打印機、內存位圖等)的訪問,借助CDC我們可以在屏幕上繪制圖像。
自定義控件中采用了雙緩沖技術,防止在繪制圖像時屏幕閃爍。其中用到了關鍵的幾個類和函數,如下。
//成員變量
CDC m_ImageDC; // 源圖片DC
HBITMAP m_hImageBitmap; // 位圖
BITMAPINFO* m_pBitmapInfo;
void* m_pBuffdata; // 圖像數據CDC m_buffDC_image; // 二級緩存
CBitmap m_cBitmap;
圖像放在控件上,圖像的大小和控件的大小可能不一樣,這時需要將圖像進行壓縮或放繪制到控件上(需要直接操作數據,調用Windows API),故在第一個CDC中使用了HBITMAP
位圖。創建位圖調用Windows API接口函數 CreateDIBSection()
函數。該函數直接綁定數據指針m_pBuffdata
,可以直接操作數據。
計算好壓縮因子、視口、窗口顯示區域,通過StretchBlt()
函數進行壓縮到m_ImageDC中,再通過BitBlt()
函數將m_ImageDC復制到m_buffDC_image,再將m_buffDC_image復制到屏幕上。
進行放大操作就是調節視口區域,再計算壓縮因子進而計算窗口顯示區域,再把圖像放到指定區域。
二、計算視口
//成員變量
CSize m_tSizeWnd; // 控件顯示窗口的尺寸
CSize m_tSizeImg; // 圖片的尺寸
int m_iImageSize; // 圖片的字節數CRect m_reShowImage; // 顯示圖像區域(視口)
CRect m_reShowWnd; // 顯示窗口區域double m_dChangeNum; // 壓縮因子(變化因子)
要求:將圖像顯示在控件上,并且保證在控件上居中。
計算縮放因子
圖像不一定是完美契合控件大小,要等比例縮放需計算縮放因子。
計算窗口的寬高和圖像寬高的比例,為了圖像全部顯示在控件上,我們選擇最小的比例當縮放因子。
//計算縮放因子
double dChangeX = (double)m_tSizeWnd.cx / m_tSizeImg.cx;
double dChangeY = (double)m_tSizeWnd.cy / m_tSizeImg.cy;m_dChangeNum = min(dChangeX, dChangeY);
計算視口(圖像全部顯示在空間上的視口大小)
圖像的顯示區域就是視口,圖像完全顯示到控件上,及視口就是整個圖像的區域。
//視口大小(相對于圖像的坐標)
m_reShowImage.left = 0;
m_reShowImage.top = 0;
m_reShowImage.right = m_reShowImage.left + m_tSizeImg.cx;
m_reShowImage.bottom = m_reShowImage.top + m_tSizeImg.cy;
計算控件顯示圖像的區域(居中)
第一步:通過壓縮因子計算壓縮后圖像的寬高
第二步:通過控件的寬高-壓縮后圖像的寬高再除以2,得到居中后的坐標
double dWid = static_cast<double>(m_reShowImage.right - m_reShowImage.left) * m_dChangeNum;
double dHit = static_cast<double>(m_reShowImage.bottom - m_reShowImage.top) * m_dChangeNum;int iWid = static_cast<int>(dWid + 0.5); // 四舍五入
int iHit = static_cast<int>(dHit + 0.5);m_reShowWnd.left = (m_tSizeWnd.cx - iWid) >> 1;
m_reShowWnd.top = (m_tSizeWnd.cy - iHit) >> 1;
m_reShowWnd.right = m_reShowWnd.left + iWid;
m_reShowWnd.bottom = m_reShowWnd.top + iHit;
再調用第一節說的StretchBlt()
函數將圖像縮放復制到二級緩存中
::StretchBlt(m_buffDC_image.GetSafeHdc(), m_reShowWnd.left,m_reShowWnd.top,m_reShowWnd.Width(),m_reShowWnd.Height(),m_ImageDC.GetSafeHdc(),m_reShowImage.left,m_reShowImage.top,m_reShowImage.Width(), m_reShowImage.Height(),SRCCOPY);
三、放大操作
放大操作:計算新視口
當鼠標點擊圖像時,圖像放大。鼠標指向的區域是相對圖像坐標是不變的,通過這個坐標計算新的視口。
補充:當圖像需要放大兩倍時,就是視口區域縮小兩倍,再顯示控件上就是放大兩倍的效果。
重寫鼠標點擊事件,我們可以得到鼠標點擊的坐標(x,y)。因為是等比例縮放,通過縮放因子計算可以得到W2和H2。及可以算出坐標到新視口邊的距離。
再計算之前需要將坐標轉換為圖像坐標系上的坐標。(圖上畫的兩個矩形都是圖像)
坐標轉換(控件上的坐標轉換成圖像上的坐標)
//將窗口的坐標轉化為圖像上的坐標
//因為圖像是通過縮放因子等比例縮放顯示在控件上的,故可以通過這層關系
//計算相對于圖像上的坐標
void MyImageView::OnLButtonDblClk(UINT nFlags, CPoint point)
{point.x /= m_dChangeNum;point.y /= m_dChangeNum;
}
計算新的縮放因子、計算新的視口大小
double dOldChangeNum = m_dChangeNum;
double newScale = 2 * m_dChangeNum;
newScale = max(m_dMinScale, min(m_dMaxScale, newScale));
double scaleRatio = dOldChangeNum / newScale; // 1/2//通過新的縮放因子得出新視口的寬高
int iWid = m_reShowImage.Width() * scaleRatio;
int iHit = m_reShowImage.Height() * scaleRatio;//通過比例關系 計算出視口的區域坐標
m_reShowImage.left = point.x - (point.x * iWid / m_tSizeImg.cx);
m_reShowImage.top = point.y - (point.y * iHit / m_tSizeImg.cy);
m_reShowImage.right = m_reShowImage.left + iWid;
m_reShowImage.bottom = m_reShowImage.top + iHit;
至此計算出新視口的區域,再進行計算新顯示窗口區域就可以完成放大操作。
代碼中初始化操作(方便以后cv)
CDC* pDC = pWnd->GetDC();m_pBitmapInfo = (BITMAPINFO*)malloc(sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * 255);if (NULL != m_pBitmapInfo){for (int i = 0; i < 256; i++){m_pBitmapInfo->bmiColors[i].rgbRed = i;m_pBitmapInfo->bmiColors[i].rgbGreen = i;m_pBitmapInfo->bmiColors[i].rgbBlue = i;}m_pBitmapInfo->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);m_pBitmapInfo->bmiHeader.biBitCount = (model == false) ? 8 : 24;m_pBitmapInfo->bmiHeader.biPlanes = 1;m_pBitmapInfo->bmiHeader.biCompression = 0;m_pBitmapInfo->bmiHeader.biXPelsPerMeter = 0;m_pBitmapInfo->bmiHeader.biYPelsPerMeter = 0;m_pBitmapInfo->bmiHeader.biClrImportant = 0;m_pBitmapInfo->bmiHeader.biClrUsed = 0;m_pBitmapInfo->bmiHeader.biHeight = tSizeImg.cy;m_pBitmapInfo->bmiHeader.biWidth = tSizeImg.cx;int bitCount = (model == false) ? 8 : 24;m_iImageWidthStep = ((tSizeImg.cx * bitCount / 8) + 3) & ~3;m_pBitmapInfo->bmiHeader.biSizeImage = m_iImageWidthStep * tSizeImg.cy;m_ImageDC.CreateCompatibleDC(pDC);m_ImageDC.SetStretchBltMode(HALFTONE); //( COLORONCOLOR ); //(HALFTONE);m_ImageDC.SetBkMode(TRANSPARENT);if (m_hImageBitmap){DeleteObject(m_hImageBitmap);m_hImageBitmap = NULL;}m_hImageBitmap = CreateDIBSection(m_ImageDC.m_hDC, m_pBitmapInfo, DIB_RGB_COLORS, &m_pBuffdata, NULL, 0);m_iImageSize = m_iImageWidthStep * tSizeImg.cy * sizeof(unsigned char);memset(m_pBuffdata, 0, m_iImageSize);m_ImageDC.SelectObject(m_hImageBitmap);}//二級緩存初始化m_cBitmap.CreateCompatibleBitmap(pDC, m_tSizeWnd.cx, m_tSizeWnd.cy);m_buffDC_image.CreateCompatibleDC(pDC);m_buffDC_image.SetStretchBltMode(HALFTONE); //( COLORONCOLOR ); //(HALFTONE);m_buffDC_image.SetBkMode(TRANSPARENT);m_buffDC_image.SelectObject(&m_cBitmap);
注:需要重寫這兩個函數
virtual void DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct);//這個函數中 設置格式SS_OWNERDRAW 之后 ,調用Invalidate(false)
//才會去執行DrawItem()函數
virtual void PreSubclassWindow()
{ModifyStyle(SS_TYPEMASK,SS_OWNERDRAW | SS_NOTIFY, SWP_FRAMECHANGED);CStatic::PreSubclassWindow();
}