PomeloWu原作,轉載請指明出處
用MFC很容易把當前屏幕截取,並顯示在自己程序的UI上。以對話框爲例,在執行繪製的單元(比如OnPaint)中調用下面這個函數就能做到:<?xml:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />
BOOL CSrnShotDlg::GetMyScreen( CDC *pdc // 目標DC ) { CDC dc; dc.CreateDC("DISPLAY", NULL, NULL, NULL); // 屏幕DC
CRect clientRect; GetClientRect(clientRect); // 對話框矩形區域
pdc->BitBlt(0, 0, // 起始位置 clientRect.Width(),clientRect.Height(), // 寬高 &dc, // 源CDC對象 0, 0, // 源位置 SRCCOPY // 複製方法 ); dc.DeleteDC(); } |
接下來改造一下,把屏幕截圖先轉換爲灰度(Gray Scale)圖,再顯示出來。轉換灰度圖的公式是,對一個RGB值,R、G、B分別是其3色分量,計算:
Gray = R * 0.299 + G *0.587 + B * 0.114
然後將Gray分別替換掉原來的3色分量。到這個地方,很自然想到用SetPixel/GetPixel來實現。因爲要對DC進行操作,當然就不能直接在上面GetMyScreen裏邊的dc直接操作了,爲此對GetMyScreen進行一下改造,並且,爲了程序的可讀性,增加一個ConvertToGray函數負責轉換(與上面代碼不同的地方用紅色區分):
void ConvertToGray (CDC * pdc) { for (int xx = 0; xx < clientRect.right ; xx ++) for (int yy = 0; yy < clientRect.bottom ; yy ++) { COLORREF crTemp = pdc->GetPixel(xx,yy); BYTE pixelR = GetRValue(crTemp); BYTE pixelG = GetGValue(crTemp); BYTE pixelB = GetBValue(crTemp); BYTE gray = (BYTE) (pixelR * 0.299 + pixelG * 0.587 +pixelB * 0.114); pdc->SetPixelV(xx,yy,RGB(gray, gray, gray)); } }
BOOL CSrnShotDlg::GetMyScreen( CDC *pdc // 目標DC ) { CDC dc; dc.CreateDC("DISPLAY", NULL, NULL, NULL); // 屏幕DC
CRect clientRect; GetClientRect(clientRect); // 對話框矩形區域
CDC *pMemDC = NULL; // 兼容DC
pMemDC = new CDC; if (!pMemDC) return FALSE; pMemDC->CreateCompatibleDC(&dc); ShowWindow(SW_HIDE); pMemDC->BitBlt(0, 0, clientRect.Width(), clientRect.Height(), &dc, 0, 0, SRCCOPY);
ConvertToGray(pMemDC);
pdc->BitBlt(0, 0, // 起始位置 clientRect.Width(),clientRect.Height(), // 寬高 pMemDC, // 源CDC對象 0, 0, // 源位置 SRCCOPY // 複製方法 ); pMemDC->DeleteDC(); delete pMemDC; dc.DeleteDC(); return TRUE; } |
效果出來了,但是並不完美。實際上我用SetPixelV代替了SetPixel,但顯示的速度還是很慢,CPU使用率也很高。如何提高效率呢?直接改DC上附着的位圖數據似乎是個好辦法。下面就轉而對CBitmap類對象進行操作。
因爲是直接截屏,所以需要先用CDC::GetDeviceCaps帶BITSPIXEL參數獲得屏幕色深,因爲不同色深的位圖的儲存方式不同。簡要說明一下:16位色位圖,每個象素佔2字節;24位色,每個象素佔3字節;32位色,每個象素佔4字節儲存空間。我們可以用CBitmap::GetBitmapBits函數來獲得位圖數據,這其實是一個BYTE數組。這個數組的結構,最簡單的是24位色的情況。前面說過了每個象素佔3個字節,按數組下標從低到高分別是B、G、R這3色分量,而32位色的情況跟24位色類似,4個字節只不過多了一個alpha值。下面就是處理24位色深的ConvertToGray24。
#define BITS24 (int)(1024 * 768 * 3) void ConvertToGray24(CBitmap *pBmp) { LPBYTE lpbits = NULL; lpbits = new BYTE[BITS24]; if (!lpbits) return;
ZeroMemory(lpbits, BITS24); pBmp->GetBitmapBits(BITS24, lpbits); for (int index = 0, j = 0, k = 0; index < BITS24; index ++) { lpbits[index] = (BYTE)(0.114 * lpbits[index]); j = index + 1; k = index + 2; lpbits[j] = (BYTE)(0.587 * lpbits[j]); lpbits[k] = (BYTE)(0.299 * lpbits[k]); lpbits[index] += lpbits[j] + lpbits[k]; lpbits[j] = lpbits[index]; lpbits[k] = lpbits[index]; index = k; }
pBmp->SetBitmapBits(BITS24, lpbits); delete [] lpbits; } |
當GetDeviceCaps(BITSPIXEL)返回16的時候,又有兩種情況:16位色和15位色。16位色的情況下,位圖數組使用2字節保存數據,其中從高位往低位分別是B、G、R這3色分量按位5:6:5佔用。需要用位操作來獲得每個分量的色值:
#define GetRValueX(rgb) ((BYTE)(rgb) & 0x1f) #define GetGValueX(rgb) ((BYTE)(((rgb) & 0x07E0) >> 5)) #define GetBValueX(rgb) ((BYTE)(((rgb) & 0xF800) >> 11)) #define RGBX(r,g,b) / ((WORD)(((BYTE)(r)|((WORD)((BYTE)(g))<<5))|(((WORD)(BYTE)(b))<<11))) |
要注意的是因爲綠色分量佔用了6bit,其儲存精度是其它兩個分量的2倍,所以在進行後繼的計算的時候公式的因數會有所改變。(另外,使用15位色的適配器比較少,其儲存規則也是佔用2字節,但是最高位無意義,其餘15位按5:5:5分配,這裏不詳細討論了。)
#define BITS16 (int)(1024 * 768 * 2) void ConvertToGray16(CBitmap *pBmp) { LPBYTE lpbits = NULL; WORD *wBits; lpbits = new BYTE[BITS16]; if (!lpbits) return;
ZeroMemory(lpDibits, BITS16); pBmp->GetBitmapBits(BITS16, lpbits); for (int index = 0, j = 0, k = 0; index < BITS16; index ++) { wBits = (WORD *)(lpbits + index); BYTE pixelR = GetRValueX(*wBits) * 2; BYTE pixelG = GetGValueX(*wBits) ; // 注意係數 BYTE pixelB = GetBValueX(*wBits) * 2; BYTE gray =(BYTE) (pixelR * 0.299 + pixelG * 0.587 +pixelB * 0.114); *wBits = RGBX(gray / (BYTE)2, gray, gray / (BYTE)2); index ++; } pBmp->SetBitmapBits(BITS16, lpbits); delete [] lpbits; }
|
最後,第三次改造GetMyScreen:
BOOL CSrnShotDlg::GetMyScreen( CDC *pdc // 目標DC ) { CDC dc; dc.CreateDC("DISPLAY", NULL, NULL, NULL); // 屏幕DC
CRect clientRect; GetClientRect(clientRect); // 對話框矩形區域
CDC *pMemDC = NULL; // 兼容DC CBitmap *pBmp = NULL; // 兼容位圖
pMemDC = new CDC; if (!pMemDC) return FALSE; pMemDC->CreateCompatibleDC(&dc);
pBmp = new CBitmap; if (!pBmp) { pMemDC->DeleteDC(); delete pMemDC; return FALSE; } pBmp->CreateCompatibleBitmap(&dc, clientRect.Width(),clientRect.Height()); pMemDC->SelectObject(pBmp); ShowWindow(SW_HIDE); pMemDC->BitBlt(0, 0, clientRect.Width(), clientRect.Height(), &dc, 0, 0, SRCCOPY);
switch(pMemDC->GetDeviceCaps(BITSPIXEL)) { case: 16 ConvertToGray16(pBmp); break; case: 24 ConvertToGray24(pBmp); break; case: 32 ConvertToGray32(pBmp); //未給出 break; default: pBmp->DeleteObject(); pMemDC->DeleteDC(); delete pBmp; delete pMemDC; dc.DeleteDC(); return FALSE; } pdc->BitBlt(0, 0, // 起始位置 clientRect.Width(),clientRect.Height(), // 寬高 pMemDC, // 源CDC對象 0, 0, // 源位置 SRCCOPY // 複製方法 );
pBmp->DeleteObject(); pMemDC->DeleteDC(); delete pBmp; delete pMemDC; dc.DeleteDC(); return TRUE; } |
附,感謝puhuofeie的幫助