GDI基础(3):绘制图片
1、
CBitmap位图类封装了Windows GDI中的位图和操作位图的成员函数。CPen、CBrush、CFont、CBitmap是常用的Windows GDI对象,和CFont一样,CBitmap也需要且只能通过其初始化函数来初始化之后才能使用,而CPen、CBrush可以直接在其构造函数中进行初始化。
CBitmap常用的初始化函数有LoadBitmap()、CreateCompatibleBitmap()、CreateBitmap()等:
LoadBitmap()加载一个位图资源来初始化位图对象。
CreateCompatibleBitmap()初始化一个位图使之与指定设备描述表兼容。
CreateBitmap()用一个指定宽度、高度和位图模式的依赖于设备的内存位图初始化位图对象。
GetBitmap()可以获得当前的位图信息到一个BITMAP结构。
2、
在窗口上显示bmp图片:一般方法为用该图片初始化CBitmap位图对象,再将这个位图对象选入兼容DC中(相当与绘制到了兼容DC中),再使用BitBlt将兼容DC上内容绘制到当前DC上。
如果要在窗口上显示多张图片或绘制多项内容则一般使用双缓冲技术:将所有图片全部绘制到兼容DC上后再一次性的将兼容DC内容绘制到当前窗口上,而不是一个一个的将图片绘制到当前窗口上。
void CtestDlg::OnPaint() { CPaintDC dc(this); //创建位图对象并加载图片进行初始化 CBitmap bitmap; bitmap.LoadBitmap(IDB_BITMAP1);//参数为图片资源名称 //获得图片大小等信息 BITMAP bp; bitmap.GetBitmap(&bp); //创建与当前DC兼容的DC CDC dcCompatible; dcCompatible.CreateCompatibleDC(&dc); //将位图对象选入到兼容DC中 CBitmap* pOldBitmap = dcCompatible.SelectObject(&bitmap); //将兼容DC中位图绘制到当前DC中 dc.BitBlt(100, 100, bp.bmWidth, bp.bmHeight, &dcCompatible, 0, 0, SRCCOPY); //删除 dcCompatible.SelectObject(pOldBitmap); dcCompatible.DeleteDC(); bitmap.DeleteObject(); }
保存窗口上的内容到位图对象:有时候需要保存窗口上指定区域的内容以供后面使用,如再将其显示到窗口上。eg:
void CFangDaDlg::ScreenShot() { CPaintDC dc(this); //创建位图对象并初始化,使之与当前DC兼容 CBitmap m_MemBitmap; m_MemBitmap.CreateCompatibleBitmap(&dc, 280, 320);//m_MemBitmap是CBitmap对象 //创建与当前DC兼容的DC CDC dcCompatible; dcCompatible.CreateCompatibleDC(&dc); //将位图选入到兼容DC中 CBitmap* pOldBitmap = dcCompatible.SelectObject(&m_MemBitmap); //绘制窗口内容到兼容DC中,窗口内容即绘制到位图对象m_MemBitmap中 dcCompatible.BitBlt(0, 0, 280, 320, &dc, 100, 100, SRCCOPY); dcCompatible.SelectObject(pOldBitmap); dcCompatible.DeleteDC(); m_MemBitmap.DeleteObject(); }
从以上可知,我们一般绘图的时候不是直接将图片绘制到窗口DC上,而是先将图片绘制到兼容DC上,再通过兼容DC将图片内容绘制到窗口DC上,保存窗口上内容到CBitmap位图对象亦是如此。而且在创建一个兼容DC后,必须将CBitmap位图对象选入兼容DC后才能向兼容DC绘制。
3、
前面说过,所有的GDI对象类都包含FromHandle静态成员函数用来通过对象句柄获得对象指针从而,而且CDC和CWnd等也包含此静态成员函数。CDC类还有一个成员函数Attach(),它也可以实现通过DC句柄来操作DC对象的功能。CDC::Attach(HDC hDC)的作用是将hDC附加到当前CDC对象,从而获得这个hDC的管理权,这样对当前CDC对象的操作就是对参数hDC的操作。当不需要操作的时候使用CDC::Detach()来分离hDC。例如下边的例子:
void DoubleBufferDraw(HDC hDC) { CDC* pDC = new CDC; pDC->Attach(hDC); //对pDC进行操作即是对hDC进行操作 //...... pDC->Detach(); delete pDC; }
4、也可以使用CImage类来绘制图片,只需定义一个CImage对象,然后调用CImage::Load()方法加载一个图片,调用CImage::Draw()方法绘制一个图片。如:
RECT rectDest; rectDest.left = rectDest.top = 0; rectDest.right = rectDest.bottom = 100; CImage img; img.Load(_T("back.png")); img.Draw(::GetDC(m_hWnd), rectDest);
CImage::Load()有两个重载函数,一个是从指定路径加载图片,一个是从输入流加载图片,当图片包含在项目资源中的时候可以使用第二个方法。如:
//从资源中加载图片 BOOL LoadImageFromResource(CImage *pImage, UINT nResID, LPCTSTR lpTyp) { if (pImage == NULL) return false; pImage->Destroy(); // 查找资源 HRSRC hRsrc = ::FindResource(hInst, MAKEINTRESOURCE(nResID), lpTyp); if (hRsrc == NULL) return false; // 加载资源 HGLOBAL hImgData = ::LoadResource(hInst, hRsrc); if (hImgData == NULL) { ::FreeResource(hImgData); return false; } // 锁定内存中的指定资源 LPVOID lpVoid = ::LockResource(hImgData); LPSTREAM pStream = NULL; DWORD dwSize = ::SizeofResource(hInst, hRsrc); HGLOBAL hNew = ::GlobalAlloc(GHND, dwSize); LPBYTE lpByte = (LPBYTE)::GlobalLock(hNew); ::memcpy(lpByte, lpVoid, dwSize); // 解除内存中的指定资源 ::GlobalUnlock(hNew); // 从指定内存创建流对象 HRESULT ht = ::CreateStreamOnHGlobal(hNew, TRUE, &pStream); if (ht != S_OK) { GlobalFree(hNew); } else { // 加载图片 pImage->Load(pStream); if (pImage->GetBPP() == 32) { int i; int j; for (i = 0; i < pImage->GetWidth(); i++) { for (j = 0; j < pImage->GetHeight(); j++) { byte *pByte = (byte *)pImage->GetPixelAddress(i, j); pByte[0] = pByte[0] * pByte[3] / 255; pByte[1] = pByte[1] * pByte[3] / 255; pByte[2] = pByte[2] * pByte[3] / 255; } } } GlobalFree(hNew); } // 释放资源 ::FreeResource(hImgData); return true; } RECT rectDest; rectDest.left = rectDest.top = 0; rectDest.right = rectDest.bottom = 100; CImage img; LoadImageFromResource(&Img, IDB_PNG1, _T("PNG")); img.Draw(::GetDC(m_hWnd), rectDest);
还可以利用CImage类来进行窗口的截屏,其可以保存为多种图片格式,如jpeg、png等。以下是使用CImage类来实现屏幕截屏的功能:
bool PrintScreen() { CString strPicName(_T("screen.jpg")); HDC hdcScreen = ::GetDC(NULL); //GetDeviceCaps函数可以获得指定DC的相关信息 int nBitPerPixel = GetDeviceCaps(hdcScreen, BITSPIXEL);//像素位数 int nWidth = GetDeviceCaps(hdcScreen, HORZRES);//水平像素总数 int nHeight = GetDeviceCaps(hdcScreen, VERTRES);//垂直像素总数 CImage image; image.Create(nWidth, nHeight, nBitPerPixel); BitBlt(image.GetDC(), 0, 0, nWidth, nHeight, hdcScreen, 0, 0, SRCCOPY); ::ReleaseDC(NULL, hdcScreen); image.ReleaseDC(); HRESULT hRes = S_FALSE; hRes = image.Save(strPicName, Gdiplus::ImageFormatJPEG); if(SUCCEEDED(hRes)) return true; else return false; }
5、GDI+绘图的一个示例流程:
先添加头文件和链接库:
#include <comdef.h> //win32程序必须包含该头文件
#include <GdiPlus.h>
using namespace Gdiplus;
#pragma comment(lib, "gdiplus.lib")
在应用程序初始化InitInstance()之前加上以下三行:
ULONG_PTR gdiplusToken;
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
在OnPaint或OnDraw中添加绘制功能:
Graphics graphics(::GetDC(m_hWnd));
Bitmap background(L"background.png");
graphics.DrawImage(&background, 0, 0, background.GetWidth(), background.GetHeight());
6、BitBlt()系列函数
CDC::BitBlt()函数的功能是将源设备上下文的位块数据复制到当前设备上下文中。函数原型:
BOOL BitBlt( int xDest, //目标矩形区域左上角x坐标 int yDest, //目标矩形区域左上角y坐标 int nWidth, //目标(源)矩形区域逻辑宽度 int nHeight, //目标(源)矩形区域逻辑高度 CDC* pSrcDC, //源设备上下文 int xSrc, //源矩形区域左上角x坐标 int ySrc, //源矩形区域左上角y坐标 DWORD dwRop //复制模式 );
最后一个参数dwRop为复制模式(或叫做光栅操作模式),可以为以下取值:
SRCCOPY:将源矩形区域直接拷贝到目标矩形区域。
BLACKNESS:表示使用与物理调色板的索引0相关的色彩来填充目标矩形区域,(对缺省的物理调色板而言,该颜色为黑色)。
DSTINVERT:表示使目标矩形区域颜色取反。
NOTSRCCOPY:将源矩形区域颜色取反,于拷贝到目标矩形区域。
MERGECOPY:表示使用布尔型的AND(与)操作符将源矩形区域的颜色与特定模式组合一起。\
........
CDC::StretchBlt()在BitBlt()的基础上增加了绘制时的放缩功能:如果目标矩形区域比源矩形区域大或小,则BitBlt()不会对源矩形区域进行拉伸或压缩, StretchBlt()则多了两个参数来指示源矩形区域大小,在复制时可以自动拉伸和压缩源像素区域,以适合目的矩形区域的尺寸。
BOOL StretchBlt( int xDest, //目标矩形区域左上角x坐标 int yDest, //目标矩形区域左上角y坐标 int nWidthDest, //目标矩形区域逻辑宽度 int nHeightDest, //目标矩形区域逻辑高度 CDC* pSrcDC, //源设备上下文 int xSrc, //源矩形区域左上角z坐标 int ySrc, //源矩形区域左上角y坐标 int nSrcWidth, //源矩形区域宽度 int nSrcHeight, //源矩形区域宽度 DWORD dwRop //复制模式 );
CDC::TransparentBlt()在StretchBlt()的基础上增加了对于指定颜色绘制时的过滤:其最后一个参数用来指定要过滤的颜色,即遇到这种颜色就绘制透明来替代,eg:\
dc.TransparentBlt(0, 0, 91, 87, srcDC, 0, 0, 91, 87, RGB(0xff,0,0));//设置红色为透明色,即过滤掉红色
CDC::AlphaBlend()在StretchBlt()的基础上增加了绘制时透明的效果:其最后一个参数用来设置绘制时的透明效果。
BOOL AlphaBlend( int xDest, int yDest, int nDestWidth, int nDestHeight, CDC* pSrcDC, int xSrc, int ySrc, int nSrcWidth, int nSrcHeight, BLENDFUNCTION blend );
7、调色板
8、刷新率
我们常说的屏幕刷新率为70Hz,分辨率为640×480,意思是说电子束每秒重复扫描屏幕70次,每行要扫描640个象素,一共有480行。
fps与刷新率类似,动态画面是由一帧一帧的画面组成的,fps就是指画面每秒刷新的帧数。一般游戏的帧数最低应保持30以上,否则会出现画面不流畅的现象,而电影、电视中的帧数一般为24、25。
9、透明
可以使用上面说到的CDC::AlphaBlend()来使绘制图片的时候产生透明效果,效果如图所示:
可以使用SetLayeredWindowAttributes()函数来设置整个窗口显示时的透明度,它会使整个窗口框架(包括其上的子控件)透明,代码及效果如下所示:
BOOL CMFCApplication10Dlg::OnInitDialog() { ....... // TODO: 在此添加额外的初始化代码 //添加WS_EX_LAYERED属性 LONG NewLong = GetWindowLong(m_hWnd, GWL_EXSTYLE) | WS_EX_LAYERED; SetWindowLong(m_hWnd, GWL_EXSTYLE, NewLong); // 取得SetLayeredWindowAttributes函数指针 typedef BOOL(WINAPI * SLWA)(HWND, COLORREF, BYTE, DWORD); SLWA pFun = NULL; HINSTANCE m_hInst = LoadLibrary(_T("User32.DLL")); if (m_hInst) { pFun = (SLWA)GetProcAddress(m_hInst, "SetLayeredWindowAttributes"); if (pFun) { int degree = 150;//透明度:0-255 pFun(m_hWnd, RGB(0, 0, 0), degree, LWA_ALPHA); } FreeLibrary(m_hInst); } ....... }