关于内存DC的使用(一)
在windows下搞图形界面的设计难免要使用到内存DC,将所有的绘制工作先绘制在内存DC上,然活一次性拷贝到屏幕DC上。可消除一些图形的闪烁问题,当然还有其他的用处,比如简单游戏中的象素碰撞检测等等。
1,创建兼容DC
CDC m_MenDC;
CDC m_MenDC2;
CDC m_MenDCMap;
CDC m_MenDCMask;
//这个是一个要创建的兼容位图
CBitmap m_Bitmap1;
m_MenDC.CreateCompatibleDC(GetDC());
m_MenDCMap.CreateCompatibleDC(GetDC());
m_MenDCMask.CreateCompatibleDC(GetDC());
m_MenDC2.CreateCompatibleDC(GetDC());
m_Bitmap1.CreateCompatibleBitmap(GetDC(),1024,768);
2,为兼容DC选入一张位图,或兼容位图。
m_MenDC.SelectObject(m_hbmpBK);
m_MenDC2.SelectObject(m_Bitmap1);
3,好了,现在可以在兼容DC上做绘制工作了,
m_btnReturn.DrawButton(m_MenDC); //绘制按钮
m_btnUp.DrawButton(m_MenDC);
m_btnLeft.DrawButton(m_MenDC);
m_btnRight.DrawButton(m_MenDC);
m_btnDown.DrawButton(m_MenDC);
m_btnBack.DrawButton(m_MenDC);
m_btnAnew.DrawButton(m_MenDC);
m_btnFull.DrawButton(m_MenDC);
m_MenDC2.BitBlt(0,0,1024,768,&m_MenDC,0,0,SRCCOPY);
// 绘制背景 DrawPath(&m_MenDCMap); //绘制路径
if(m_bFullView)
{
SetStretchBltMode(m_MenDC2.GetSafeHdc(),HALFTONE );
m_MenDC2.StretchBlt(m_rtMap.left,m_rtMap.top,m_rtMap.Width(),m_rtM ap.Height(), &m_MenDCMap,0,0,m_iMapWidth,m_iMapHeight,SRCCOPY); //绘制平面图,查看全图
}
else
{
m_MenDC2.BitBlt(m_rtMap.left,m_rtMap.top,m_rtMap.Width(),m_rtMap.Hei ght(), &m_MenDCMap,m_xOffset,m_yOffset,SRCCOPY); //绘制平面图
}
4,绘制结果的显示,将这些东西拷到屏幕DC上,所谓的双缓冲就是把所有的绘制工作都做在一个内存DC上。 // 最后一次拷到屏幕DC上,只能有一次
dc.BitBlt(0,0,1024,768,&m_MenDC2,0,0,SRCCOPY); 这里我所强调的“一次”;是不要同时将几个DC的内容都拷到屏幕DC上,这样没有起到双缓冲的效果。如果你搞了很多个内存DC,想把这些东西都显示出来,那你应该先把这多个内存DC的内容同时拷到另外一个内存DC上,再把这个内存DC的内容拷到屏幕DC上。
5,注意的一点:
m_hbmpBK=HBITMAP)::LoadImageAfxGetInstanceHandle),path+"Bk4.bmp",IMAG E_BITMAP,0,0,LR_LOADFROMFILE);
m_MenDC.SelectObject(m_hbmpBK);
m_MenDC.绘制工作。
在内存DC上所做的绘制工作实际上都是绘制在你选入的那张位图m_hbmpBK 上。就是说m_hbmpBK 句柄所指向的位图内容改变了,如果以后你在其他的地方在用这个m_hbmpBK 的时候,你会发现这已经不是你想象中的Bk4.bmp了,^_^。如果想再用原图,必须的重新从硬盘导入。
还要注意的一点是,一个位图句柄不可同时选入到两个内存DC中。内存DC对于选入的位图具有排他性,如果你m_MenDC.SelectObject(m_hbmpBK);而m_hbmpBK 已经被其他的内存DC选用了话,那刚才这句m_MenDC.SelectObject(m_hbmpBK);是毫无意义的。
在网上看到了许多的关于装位图装载到离屏表面的文章,但是都是使用了WIN32函数,虽然有效,但不是很通用。如果我们要装载其它的格式的文件使用不了WIN32函数,不就无能为力了吗?于是我想直接操作文件,直接读取位图文件的数据到离屏表面。网上还是有这样的文章的,但是很少,并且没有过多的说明。
其实,装载文件到离屏表面也很简单,不需要什么算法知识就可以完成的。主要的方法是(以256色位图为准):
1、将位图文件的颜色表和图像数据读入内存。
2、创建前后表面,离屏表面。
3、根据颜色表创建调色板。
4、将位图数据传输到离屏表面。
5、利用后表面的BLTFAST将图像输出到后表面。
6、翻转表面,将图像显示到屏幕。
先声明几个变量:
BITMAPFILEHEADER bmfh; //位图文件头
BITMAPINFOHEADER bmih; //位图信息头
RGBQUAD rgb[256]; //颜色表
首先,我了解了一下位图文件的结构,其实也是很简单的。
1、文件头: typedef struct tagBITMAPFILEHEADER
{
UINT bfType; //文件标志
DWORD bfSize; //文件大小
UINT bfReserved1,bfReserved2;
DWORD bfOffBits; //数据偏移
}BITMAPFILEHEADER;
bfType:是位图的文件标志,为"BM"。当你从文件中读出放到一个变量中时,它是"MB",即0x4d42。在内存中,存放是高位在前的。所以在磁盘上是"BM",读到内存就为"MB"了,不信?你一个字节一个字节的读,将这两个字节存到两个字符型变量char ch1,ch2;中,你会发现ch1=='B',ch2=='M'。
bfSize:位图文件的大小。等于位图文件头+信息头+颜色表+位数据。以字节为单位即:
sizeof(bmfh)+sizeof(bmih)+sizeof(RGBQUAD)*256+bmih.biSizeImage
bfOffBits:位图数据偏移.如果你想直接读取位图的数据.使用
fseek(fil_ptr,bmfh.bfOffBits,SEEK_SET); 这样,可以直接将文件指针指向位图数据开始的地方。
2、信息头: typedef struct tagBITMAPINFOHEADER
{
DWORD biSize; //信息头大小。40字节
LONG biWidth,biHeight; //位图实际宽、高度。
WORD biPlanes; //
WORD biBitCount; //位图每像素的位数。
DWORD biCompression; //
DWORD biSizeImage; //位数据的大小(字节)
LONG biXPelsPerMeter,biYPelsPerMeter; //
DWORD biClrUsed; //
DWORD biClrImprotant; //
}BITMAPINFOHEADER;
介绍一些重要的部分:
biSize:信息头大小,即此结构的大小。为40字节。即sizeof(bmih)。
biWidth,biHeight:位图的像素宽、高。
biBitCount:位图每像素的位数。指定了这个位图文件的颜色深度。在这个例子中是8。
biSizeImage:位图数据区的大小。这里有需要注意的:位图数据每行是以4字节增充的,如果是一个256色的位图。它的像素占一字节。如果图像宽度为80像素,则图像每行为80字节,如果图像宽度为79像素,则磁盘上的位图文件仍然是80字节。(78,77像素每行也为80字节)。图像每行76,75,74,73像素,则它在文件中占76个字节。因此在从磁盘读出数据时要知道每行的字节数,这里我使用 bytperlin=bmih.biSizeImage/bmih.biHeight; 从磁盘的位图文件中读
取数据。首先是位图文件头,信息头。使用如下语句便可:
fread(&bmfh,sizeof(bmfh),1,fil_ptr);
fread(&bmih,sizeof(bmih),1,fil_ptr);
这时两个结构bmfh,bmih就存放了我们需要的一些关于位图的信息了。再读入颜色表:
RGBQUAD* prgb; prgb=(RGBQUAD*)malloc(sizeof(RGBQUAD)*bmih.biBitCount); //注意,在使用指针之前一定要给它分配内存空间,否则的话程序会退出。
fread(prgb,sizeof(RGBQUAD),1< PALETTEENTRY*ppal= (PALETTEENTRY*) malloc(sizeof(PALETTEENTRY)*bmih.biBitCount); for(int i=0;i<(1< { ppal[i].peRed=prgb[i].rgbRed; ppal[i].peGreen=prgb[i].rgbGreen; ppal[i].peBlue=prgb[i].rgbBlue; ppal[i].peFlag=0; } 之后我们就可以通过这个结构来取得LPDIRECTDRAWPALETTE接口指针。之后为前表面设置调色板(在后面的程序中)。最后是读入位图位数据了,每个像素是1个字节。位图图像数据的大小在信息头中已经给定了:bmih.biSizeImage 这个大小包括实际的图像数据和为了每行达到4字节而扩充的字节数。 我们用如下方法读入: BYTE* pbuffer=(BYTE*)malloc(sizeof(BYTE)*bmih.biSizeImage); //注意,一定要分配内存空间,否则程序会退出!!!!!!! fread(pbuffer,sizeof(BYTE),bmih.biSizeImage,fil_ptr); 这样,位图中的数据我们全部读入了。文件头,信息头只是给我们提供了一个参数,方便我们读入位图数据。颜色表用于设置调色板。(后面有程序)。最后是将内存中的图像数据传入到离屏表面中了: DDSURFACEDESC2 ddsd; ZeroMemory(&ddsd,sizeof(ddsd)); ddsd.dwSize=sizeof(ddsd); //注意一定要初始化这个结构,否则程序会退出!! lpDDS_Off->Lock(NULL,&ddsd,DDLOCK_WAIT|DDLOCK_WRITEONLY,NULL); (BYTE*)lpSurf=(BYTE*)ddsd.lpSurface; //取离屏表面指针。 //将内存数据复制到离屏表面中 lpDDS_Off->UnLock(NULL); 这样,全部的事情完成了,想要输出离屏表面的数据到后表面,使用后表面的BltFast 就可以了。下面是具体的程序片段: //给指定的表面设置调色板 int Create_Palette(LPDIRECTDRAW7 lpDD,LPDIRECTDRAWSURFACE7& lpDDS_Front,LPDIRECTDRAWPALETTE& lpDDP,char* filnam) { RGBQUAD rgbquad[256]; PALETTEENTRY pal[256]; if(filnam=="") return 0; //从文件中读入调色板索引 FILE* fil_ptr; fil_ptr=fopen(filnam,"rb"); if(fil_ptr==NULL) return 0; fseek(fil_ptr,sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER),SEEK_ SET); fread(rgbquad,sizeof(RGBQUAD),256,fil_ptr); fclose(filnam); for(int i=0;i<256;i++) { pal[i].peBlue=rgbquad[i].rgbBlue; pal[i].peGreen=rgbquad[i].rgbGreen; pal[i].peRed=rgbquad[i].rgbRed; pal[i].peFlags=PC_NOCOLLAPSE; } lpDD->CreatePalette(DDPCAPS_8BIT,pal,&lpDDP,NULL); lpDDS_Front->SetPalette(lpDDP); return 1; } //装载位图数据,在这个函数中调用创建离屏表面的函数 (Init_Off) int LoadData_8(char* filnam) { if(filnam=="") return 0; FILE* fil_ptr; fil_ptr=fopen(filnam,"rb"); if(fil_ptr==NULL) return 0; BITMAPFILEHEADER bmfh; BITMAPINFOHEADER bmih; fread(&bmfh,sizeof(bmfh),1,fil_ptr); fread(&bmih,sizeof(bmih),1,fil_ptr); Init_Off(lpDD,lpDDS_Off,bmih.biWidth,bmih.biHeight); int bytperlin=bmih.biSizeImage/bmih.biHeight; //位图数据每行字节数BYTE* pbuffer=NULL; //////////////////////////////////////////////////////// //////////////////////////////////////////////////////// pbuffer=(BYTE*)malloc(sizeof(BYTE)*bmih.biSizeImage); fseek(fil_ptr,bmfh.bfOffBits,SEEK_SET); fread(pbuffer,sizeof(BYTE),bmih.biSizeImage,fil_ptr); fclose(fil_ptr); DDSURFACEDESC2 ddsd; memset(&ddsd,0,sizeof(ddsd)); ddsd.dwSize=sizeof(ddsd); lpDDS_Off->Lock(NULL,&ddsd,DDLOCK_WAIT|DDLOCK_WRITEONLY,NULL); BYTE* pSurf=(BYTE*)ddsd.lpSurface; BYTE* pData=pbuffer; pData+=bmih.biSizeImage; //指针已经出了数据区,回一位,就是数据区. for(int row=0;row { pData-=bytperlin; memcpy(pSurf,pData,bmih.biWidth); pSurf+=ddsd.lPitch; //表面从第一行依次向后定位 } lpDDS_Off->Unlock(NULL); return 1; } //给表面设置透明色 int SetColorKey8() { DDSURFACEDESC2 ddsd; ZeroMemory(&ddsd,sizeof(ddsd)); ddsd.dwSize=sizeof(ddsd); lpDDS_Off->Lock(NULL,&ddsd,DDLOCK_WAIT|DDLOCK_READONLY,NULL); 大家都知道内存DC是一种提高绘图效率,避免屏幕闪烁的好办法,几乎所有的绘图都必须使用到内存DC,可是在MSDN里一切都不是那么明显. 为了为以后的游 戏制作更加顺利,我研究了一下内存DC的标准用法,然后贴在这里也是一个备忘. 其实所有的内存DC的原理都像下图所示: Class CMyView { public: //内存DC(主DC,临时DC(用来装载位图)) CDC *m_PrimaryMemDC; CDC *m_TempMemDC; //位图对象 CBitmap *m_bgMap; //背景 CBitmap *m_bitMap; //人物 CBitmap *m_tempMap; //临时 CRect m_rect; } 然后便是在类的构造函数中建立对象,代码一目了然,我就不多说了. CMemDCTestView::CMemDCTestView() { m_TempMemDC = new CDC; m_PrimaryMemDC = new CDC; m_bitMap = new CBitmap; m_tempMap = new CBitmap; m_bgMap = new CBitmap; } 之后在OnCreate函数中初始化这些对象(注意不是构造函数,因为构造的时候还 没有DC),OnCrate函数默认在View类中是没有的,所以你要手动加入. CMyView::OnCrate(……) { CClientDC dc(this); //创建与内存兼容的DC m_PrimaryMemDC->CreateCompatibleDC(&dc); m_TempMemDC->CreateCompatibleDC(&dc); //创建兼容位图 m_tempMap->CreateCompatibleBitmap(&dc,1024,768); //装载位图 HBITMAP bitmap = (HBITMAP)::LoadImage(NULL,"pic\\bgmap.bmp", IMAGE_BITMAP,1024,768, LR_LOADFROMFILE); m_bgMap->Attach(bitmap); bitmap = (HBITMAP)::LoadImage(NULL,"pic\\AppExit_enu.bmp",IMAGE_BITMAP,61,67, LR_LOADFROMFILE); m_bitMap->Attach(bitmap); //载入位图 m_PrimaryMemDC->SelectObject(m_tempMap); return 0; } 我们看到这里主要做的工作是创建和窗口DC相兼容的内存DC,然后是载入位图. 提醒一点就是创建兼容位图,然后把他载入内存DC中这个过程的理解,我是这么理解的只有你的DC中已经有了一张图片,然后才能够往上面贴图片.就像我们画画的时候先要有一张白纸,然后才能画画. 还是拿画画做例子,再解释另外一个东西,使用SelectObject函数可以看成是换画纸,而BitBlt函数我们就可以看作是把其他画纸上的东西复印到现在的这个画纸上,所以你的DC必须先有画纸才能用BitBlt.这也就是为什么我刚才试了很多次都不能BitBlt成功的原因. 再往下看,当然到了最关键的OnDraw函数了。 void CMemDCTestView::OnDraw(CDC* pDC) { CMemDCTestDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); GetClientRect(&m_rect); BITMAP map; m_bgMap->GetBitmap(&map); m_TempMemDC->SelectObject(m_bgMap); m_PrimaryMemDC->BitBlt(0,0,map.bmWidth,map.bmHeight,m_TempMemDC,0,0, SRCCOPY); m_bitMap->GetBitmap(&map); m_TempMemDC->SelectObject(m_bitMap); m_PrimaryMemDC->BitBlt(0,0,map.bmWidth,map.bmHeight,m_TempMemDC,0,0,S RCCOPY); pDC->BitBlt(0,0,m_rect.Width(),m_rect.Height),m_PrimaryMemDC,0,0,SRCC OPY); } 在这个函数里我们首先定义了一个数据结构BITMAP通过CBitmap::GetBitmap 来得到图像的相关信息(主要是长和宽),之后我们把要载入的第一幅位图载入暂存DC1(m_TempMemDC)中,之后再把它贴到暂存DC2(m_PrimaryMemDC)上。(注意这时的暂存DC2已经有一个默认的位图选入了,否则贴图操作不能成功)下面的步骤的重复的贴第二张图片,以此类推,最后把暂存DC2的东西贴到窗口DC上,我猜这个窗口DC一定在程序创建的时候已经选入一张位图了(不然无法BitBlt)。好了,这就是内存DC绘图的标准用法,只是繁琐,并不是很难,大家用的时候一定要细心再细心,特别是对于选择默认位图的部分,很容易遗漏。void CPaintView::OnDraw(CDC* pDC) { CMyPaintDoc* pDoc = GetDocument(); ASSERT_VALID(pDoc); //下面是在内存区进行绘图,然后一起输出到屏幕 CDC cdcMem; cdcMem.CreateCompatibleDC(pDC); //产生内存区 CBitmap bitmap; RECT rect; GetClientRect(&rect); int x=pDC->GetDeviceCaps(HORZRES); //得到设备以像素为单位的宽度 int y=pDC->GetDeviceCaps(VERTRES); //得到设备以像素为单位的高度 bitmap.CreateCompatibleBitmap(pDC,rect.right,rect.bottom); //产生位图内存区 cdcMem.SelectObject(&bitmap); cdcMem.FillSolidRect(0,0,rect.right,rect.bottom,RGB(255,255,0));//填充背景色 pDoc->ShowAllShapes(&cdcMem);//画图,是我自已定义的画图函数 pDC->BitBlt(0,0,x,y,&cdcMem,0,0,SRCCOPY);//输出到屏幕 } 位图是一种图形化对象,用于在设备环境里创建、绘制、操纵和接收图片。从[开始按钮]上的小Winodws标志到标题栏上的[关闭]按钮,位图在Windows里无处不在。位图可以看作是一种由像素数组构成的图片,这些像素可以在屏幕上进行绘制。和所有图片一样,位图有自己的高度和宽度。也提供方法来判断位图使用什么颜色。最后,位图也是一个描述位图中每个像素的位(bits)数组。 习惯上,Windows下的位图被划分成两种类型:设备相关位图(DDBs)和设备无关位图(DIBs)。DDBs是一种和具体DC的特性有紧密关系的位图,不容易在有不同特性的DC上绘制。DIBs则相反,它与具体设备无关,因此需要携带足够的信息以便于在任何设备上准确的绘制。 Windwos CE包含了许多在其它Windows版本里可以使用的位图函数。不同之处包括只有Windows CE才支持的一种新的四色格式和对DIBs不同的操纵方式。设备相关位图可以使用CreateBitmap函数来创建设备相关位图,函数原型如下: HBITMAP CreateBitmap (int nWidth, int nHeight, UINT cPlanes, UINT cBitsPerPel, CONST VOID *lpvBits); nWidth和nHeight表示位图的尺寸。 cPlanes是一个历史产物,当时显示器采用 不同的硬件平面来实现像素里的每个颜色。对Windows CE来说,该参数必须是1。cBitspPerPel表示每个像素使用的位数。颜色数是cBitspPerPel的2次幂。在Windows CE下,允许使用的是1、24、8、16和24。正如我说过的,4色位图是Windows CE特有的,其它Windows 平台不则不支持。 最后一个参数是指向位图位数的指针。在Windows CE下,位数总是按压缩像素格式排列的。也就是,每个像素按字节存储成一系列的比特位,下一个像素紧接上一个。位数组的第一个像素是位图左上角的像素。像素沿位图顶行排列,随后是第二行,依次类推。位图每行必须是双字(4字节)对齐排列。为了对齐下一行,可在行尾使用0来填充。图2-5演示了这种排列方式,图中展示了一个126*64像素的位图,每个像素使用8位。图2-5(略) 位图里的字节布局函数CreateCompatibleBitmap,其原型如下: HBITMAP CreateCompatibleBitmap (HDC hdc, int nWidth, int nHeight); 用该函数可以创建一个格式与传入的设备环境兼容的位图。所以如果设备环境是四色DC,创建的位图也是一个四色位图。当您要在屏幕上操纵图片时,使用该函数很就很方便,因为该函数可以很容易创建一个与屏幕直接颜色兼容的空位图。 设备无关位图设备无关位图和设备相关位图之间的基本差异是存储成DIBs的图象有自己的颜色信息。自从使用BMP作为扩展名的Windows 3.0起,几乎每个位图文件都含有在Windows里创建DIB时所需要的信息。 在Windows早期,写程序手工读DIB文件并把数据转换为位图是程序员必备的技能。现在,这个烦冗的任务可以通过Windows CE特有的函数SHLoadDIBitmap 来完成,函数原型如下: HBITMAP SHLoadDIBitmap (LPCTSTR szFileName); 该函数直接从位图文件里装载位图并提供位图句柄。在Windows XP里可以使用带LR_LOADFROMFILE参数标志的LoadImage函数来完成同样的处理,但Windows CE下的LoadImage不支持这个标志。DIB片段虽然Windows CE里很容易装载位图文件,但有时您必须读屏幕上图象、操纵图象以及将图象重画到屏幕上。这是DIBs比DDBs更好一些的地方之一。虽然设备相关位图的位数据可以获取得到,但缓冲区的格式直接依赖于屏幕格式。而通过使用DIB,或者更准确地说,通过使用DIB片段,您的程序可以把位图读到预定义格式的缓冲区里,而不用担心显示设备的格式。虽然从Windows 3.0起就不断加入许多DIB创建函数,但Windows CE只支持XP中的一部分DIB函数。CreateDIBSection是这些函数中的第一个: HBITMAP CreateDIBSection (HDC hdc, const BITMAPINFO *pbmi, UINT iUsage, void *ppvBits, HANDLE hSection, DWORD dwOffset); 因为他们是相当晚才加到Win32 API里的,所以DIB片段对程序员来说可能是比较新鲜的。使用DIB片段是为了改进Winodows NT上直接操纵位图的应用程序性能。简而言之,DIB片段允许程序员在直接访问位图位数据时,选择一个设备环境里的DIB。为达到这个目的,DIB片段将一个缓冲区与内存DC结合到一起,该缓冲区同时包含了该DC的位数据。因为图象是映射到一个DC的,所以可以使用其它图形函数调用来修改图片。同时,DC里的DIB格式的原始位数据可以被直接操纵。能改进在NT上的性能固然是很好,但对Window CE程序员来说,能够简化位图的使用和操作位图的内容才是最有价值的。该函数中最重要的参数是指向BITMAPINFO结构的指针。该结构描述了设备无关位图的布局和颜色构成,该结构包含一个BITMAPINFOHEADER结构和一个代表位图使用的调色板的RGBQUAD 数组。 BITMAPINFOHEADER定义如下 typedef struct tagBITMAPINFOHEADER { DWORD biSize; LONG biWidth; LONG biHeight; WORD biPlanes; WORD biBitCount; DWORD biCompression; DWORD biSizeImage; LONG biXPelsPerMeter; LONG biYPelsPerMeter; DWORD biClrUsed; DWORD biClrImportant; } BITMAPINFOHEADER; 如你所见,该结构包含的信息远多于传给CreateBitmap的参数。第一个域是该结构的尺寸,必须由调用者填充,用于区别由OS/2管理器沿袭来的BITMAPCOREINFOHEADER结构。biWidth, biHeight, biPlanes,和biBitCount都和CreateBitmap里的同名参数类似,但有一个例外,biHeight的正负号指定了位数组的组织排列方式。如果biHeight是正数,位数组按由上到下的格式排列,这一点和CreateBitmap相同。如果biHeight是负数,位数组则按由下到上的格式排列,也就是位图的底部行定义在该位数组的首位。和CreateBitmap一样,biPlanes必须设置为1。biCompression指出位数组使用的压缩方式。Windows CE 里,允许使用的标志有,BI_RGB,指出缓冲区没有压缩;BI_BITFIELDS,指出像素格式被定义在颜色表的头三个入口里。biSizeImage用于指出位数组的大小。但是,当使用BI_RGB标志时,biSizeImage可以设置为0,表示用BITMAPINFOHEADER结构里提供的尺寸(dimensions )和像素的位数来计算数组的大小。biXPelsPerMeter和biYPelsPerMeter提供图片的准确尺寸信息。但是对于CreateBIBSection来说,这些参数可以设置为0。biClrUsed指出实际使用的调色板里的颜色数。在256色图片里,调色板有256个入口,但畏途自身可能只需要大约100个不同的颜色。这个域帮助调色板管理器--Windows管理颜色匹配的部件--将系统调色板里的颜色同位图要求的颜色进行匹配。biClrImportant 进一步指出真正需要的颜色。对更多颜色的位图,这两个域设置为0,表示使用所有颜色并且所有颜色都重要。 前面提到过,BITMAPINFOHEADER结构之后是RGBQUAD结构数组。该结构定义如下: typedef struct tagRGBQUAD { BYTE rgbBlue; BYTE rgbGreen; BYTE rgbRed; BYTE rgbReserved; } RGBQUAD 该结构允许有红蓝绿各256级色度(shade )。虽然用该结构可以创建几乎任何色度,但设备上实际渲染的颜色是受设备能显示的颜色的限制的。 总体来看,RGBQUAD结构数组描述了DIB的调色板。调色板是位图里的颜色列表。如果位图有调色板,位图数组的每个入口包含的就不再是颜色,而是包含每个像素颜色的调色板索引。虽然对单色位图来说是多余的,但当在彩色设备上绘制彩色位图时,调色板就相当重要了。例如,虽然256色位图中每个像素一个字节,但该字节指向一个代表红绿蓝色的24位值。所以尽管256色位图只能包含256个不同的颜色,但由于是使用24位调色板入口进行颜色绘制的,因而这些颜色中的每个都可使用出的1千6百万种颜色中的一个。为了方便在32位中使用,每个只包含24位颜色信息的调色板入口都被扩充到32位宽度了,这也是RGBQUAD名字的来源。(译者注:QUAD有四个一套的意思) CreateDIBSection剩余的四个参数中只有两个用于Windows CE。IUsage指出调色板里的颜色是如何被绘制的。如果该参数是DIB_RGB_COLORS,表示位图里的位数据包含了每个像素的全部RGB颜色信息;DIB_PAL_COLORS,表示位图像素包含DC里当前选择的调色板的索引。PpvBits 是指向构成位图图象的位数据的指针。最后两个参数,hSection和dwOffset,Windows CE不支持它们,必 须设置为0。在Windows的其它版本里,它们允许使用内存映射文件来给出位数据。因为Windows CE不支持内存映射文件,所以它们不能被CreateDIBSection 支持。 GetDIBColorTable和SetDIBColorTable是管理DIB调色板的两个函数,它们的原型如下: UINT GetDIBColorTable (HDC hdc, UINT uStartIndex, UINT cEntries, RGBQUAD *pColors); 和 UINT SetDIBColorTable (HDC hdc, UINT uStartIndex, UINT cEntries, RGBQUAD *pColors); 对这两个函数来说,uStartIndex指出将被设置或者查询的调色板的第一个入口。CEntries指出有多少调色板入口将改变。指向RGBQUAD数组的指针是用于设置(对SetDIBColorTable)或查询(对GetDIBColorTable)的颜色数组。 绘制位图能够创建和装载位图固然很好,但如果您创建的位图不能绘制在屏幕上,那就没什么大用处。绘制位图可能并不是您想象的那么简单。位图被绘制到屏幕DC之前,必须先将位图选进一个DC,再将其复制到屏幕设备环境里。虽然这个过程听起来可能有点费解,但这是有合理的原因的。 把位图选择到一个设备环境的过程与把逻辑字体选择到设备环境的过程类似。下面让我们把理想变成现实吧。正如Windows要为请求的字体找到最可能匹配的字 体一样,位图选择过程也必须为位图要求的颜色找到匹配的设备上可用的颜色。只有在这个过程完成后,位图才能绘制到屏幕上。为了帮助完成这一中间步骤,Windows提供了一个替身DC—内存设备环境。 要创建内存设备环境,可以使用函数CreateCompatibleDC: HDC CreateCompatibleDC (HDC hdc); 该函数创建一个与当前屏幕DC兼容的内存DC。一旦创建成功,可以使用您以前用来选择逻辑字体的SelectObject函数将源位图选进这个内存DC。最后用 BitBlt 或StretchBlt将位图从内存DC复制到屏幕DC。 位图函数的主力是 BOOL BitBlt (HDC hdcDest, int nXDest, int nYDest, int nWidth, int nHeight, HDC hdcSrc, int nXSrc, int nYSrc, DWORD dwRop); BitBlt函数发音为“bit blit”,它是一个有意思的函数,它在设备环境上操作,而不是内存里,它有时是一个很特别的函数。第一个参数是位图即将被复制到其上的目的设备环境的句柄。接下来的4个参数规定了位图最终位于的目的矩形的位置和大小。接下来的3个参数规定了源设备环境的句柄以及源图象左上角在该DC里的位置。最后一个参数dwRop规定图象如何从源设备环境复制到目的设备环境。ROP代码规定了源位图和当前目的设备如何组合来产生最终图片。ROP 代码为SRCOPY,表示简单复制源图象。ROP代码为SRCPAINT,表示源图象和目 的之间进行或操作。ROP代码为SRCINVERT,表示复制一个逻辑反转图象,本质上是一个负的源图象。一些ROP代码还将当前选择的画刷(brush)一起作为计算结果图象的因素。ROP代码很多,所以这里不可能覆盖全,要获得全部列表,请参考Windows CE编程文档。 下面的代码片段总结了如何绘制位图: // Create a DC that matches the device. hdcMem = CreateCompatibleDC (hdc); // Select the bitmap into the compatible device context. hOldSel = SelectObject (hdcMem, hBitmap); // Get the bitmap dimensions from the bitmap. GetObject (hBitmap, sizeof (BITMAP), &bmp); // Copy the bitmap image from the memory DC to the screen DC. BitBlt (hdc, rect.left, rect.top, bmp.bmWidth, bmp.bmHeight, hdcMem, 0, 0, SRCCOPY); // Restore original bitmap selection and destroy the memory DC. SelectObject (hdcMem, hOldSel); DeleteDC (hdcMem); 创建内存设备环境,即将被绘制的位图被选进该DC。因为您可能没有存储即将 被绘制的位图尺寸,通常可以调用GetObject来获得。GetObject返回关于图形对象的信息,在本例中是一个位图。可以用这个很有用的函数来查询字体和其它图象对象的信息。接下来,使用BitBlit将位图复制进屏幕DC。为了清理,需 要将位图从内存设备环境中取消选择,并使用DeleteDC将该内存DC删除。不要将DeleteDC同ReleaseDC混淆,ReleaseDC是释放一个显示DC。DeleteDC只应该和CreateCompatibleDC一起使用,ReleaseDC只应该和GetDC或GetWindowsDC 一起使用。用StretchBlt除了复制位图,还可以拉伸或者压缩位图。该函数原型如下: BOOL StretchBlt (HDC hdcDest, int nXOriginDest, int nYOriginDest, int nWidthDest, int nHeightDest, HDC hdcSrc, int nXOriginSrc, int nYOriginSrc, int nWidthSrc, int nHeightSrc, DWORD dwRop); StretchBlt里的参数和BitBlt里的基本相同,但是有一点例外的是现在可以指定源图象的宽度和高度。同样的,这里的ROP代码规定了源和目的之间如何组合来产生最终图象。 Windows CE还有另外一个位图函数TransparentImage,原型如下: BOOL TransparentImage (HDC hdcDest, LONG DstX, LONG DstY, LONG DstCx, LONG DstCy, HANDLE hSrc, LONG SrcX, LONG SrcY, LONG SrcCx, LONG SrcCy, COLORREF TransparentColor); 该函数同StretchBlt类似,但有两个很重要的例外。首先,您可以指定位图里作为透明色的颜色。当把位图复制到目标中时,位图里透明色的像素不被复制。第二个不同是hSrc参数要么是设备环境要么是位图句柄,该参数允许您不必理会在屏幕上绘制图象前必须将源图象选进一个设备环境的要求。TransparentImage与Windows 2000中的TransparentBlt函数基本相同,除了TransparetBlt不能直接使用位图作为源图象以外。 和其它版本的Windows一样,Windows CE还支持2个其它blit函数:PatBlt 和MaskBlt。PatBlt函数将当前选择的画刷同目的DC里当前图象进行组合,产生结果图象。我在本章后面会谈到画刷。MaskBlt函数与BitBlt类似,但包含一个掩码图象,用来提供只绘制源图象的一部分到目的DC里的功能。 (一)功能实现了一个定时截取当前屏幕图像的小程序。 (二)准备工作 1)建立VC CONSOLE APPLICATION,选择MFC SUPPORT 2)在STDAFX.H文件中加入头文件:conio.h (三)主程序主程序代码如下: char Filename[100]; int count = 0; while(!_kbhit())//用户按键则退出 { Sleep(5000);//挂起5秒 count ++; sprintf(Filename, "%d.bmp", count); Screen(Filename);//调用Screen函数 } 以上代码每隔5秒钟调用一次函数Screen,将当前屏幕保存到文件中。 (四)工作函数Screen Screen实现了当前屏幕内容到bmp文件的拷贝。源代码如下: void Screen(char filename[]) { CDC *pDC;//屏幕 DC pDC = CDC::FromHandle(GetDC(NULL));//获取当前整个屏幕DC int BitPerPixel = pDC->GetDeviceCaps(BITSPIXEL);//获得颜色模式 int Width = pDC->GetDeviceCaps(HORZRES); int Height = pDC->GetDeviceCaps(VERTRES); cout << "当前屏幕色彩模式为" << BitPerPixel << "位色彩" << endl << "屏幕宽度:" << Width << endl << "屏幕高度:" << Height << endl << endl; CDC memDC;//内存 memDC.CreateCompatibleDC(pDC); CBitmap memBitmap, *oldmemBitmap;//建立和屏幕兼容的 bitmap memBitmap.CreateCompatibleBitmap(pDC, Width, Height); oldmemBitmap = memDC.SelectObject(&memBitmap);//将memBitmap 选入内存DC memDC.BitBlt(0, 0, Width, Height, pDC, 0, 0, SRCCOPY);//复制屏幕图像到内存DC / //以下代码保存memDC中的位图到文件 BITMAP bmp; memBitmap.GetBitmap(&bmp);//获得位图信息 FILE *fp = fopen(filename, "w+b"); BITMAPINFOHEADER bih = {0};//位图信息头 bih.biBitCount = bmp.bmBitsPixel;//每个像素字节大小 bih.biCompression = BI_RGB; bih.biHeight = bmp.bmHeight;//高度 bih.biPlanes = 1; bih.biSize = sizeof(BITMAPINFOHEADER); bih.biSizeImage = bmp.bmWidthBytes * bmp.bmHeight;//图像数据大小 bih.biWidth = bmp.bmWidth;//宽度 BITMAPFILEHEADER bfh = {0};//位图文件头 bfh.bfOffBits = sizeof(BITMAPFILEHEADER) +sizeof(BITMAPINFOHEADER);//到位图数据偏移量 bfh.bfSize = bfh.bfOffBits + bmp.bmWidthBytes * bmp.bmHeight;//文件总的大小 bfh.bfType = (WORD)0x4d42; fwrite(&bfh, 1, sizeof(BITMAPFILEHEADER), fp);//写入位图文件头 fwrite(&bih, 1, sizeof(BITMAPINFOHEADER), fp);//写入位图信息头 byte * p = new byte[bmp.bmWidthBytes * bmp.bmHeight];//申请内存保存位图数据 GetDIBits(memDC.m_hDC, (HBITMAP) memBitmap.m_hObject, 0, Height, p, (LPBITMAPINFO) &bih, DIB_RGB_COLORS);//获取位图数据 fwrite(p, 1, bmp.bmWidthBytes * bmp.bmHeight, fp);//写入位图数据 delete [] p; fclose(fp); memDC.SelectObject(oldmemBitmap); } (五)改进可以在系统热键中加入自定义热键,进行动态的(按用户需要的)截屏操作。 1、将位图加载到内存DC中 2、将内存DC中的内容绘制到要显示位图的DC中 *MFC中没有直接用来加载位图的函数,可以使用WindowsAPI中的 LoadImage函数来加载位图,其原型如下: HANDLE LoadImage( HINSTANCE hinst, //位图实体句柄,若位图在硬盘或者资源文件 //中,则将该参数设为NULL LPCTSTR lpszName, //位图所在路径及文件名,或者资源名称UINT uType, //加载位图的类型: //1、IMAGE_BITMAP //2、IMAGE_CURSOR //3、IMAGE_ICON int cxDesired, //位图宽度,像素 int cyDesired, //位图高度,像素 UINT fuLoad //加载方式比如LR_LOADFROMFILE 等 ); 步骤: CClientDC dc(this); CDC *mdc=new CDC; //CDC只有空构造函数,也就是说无法通过拷贝函数来构造一个 //与dc相同的mdc,即mdc=dc非法。 //CreateCompatibleDC建立一个适合指定pDC的内存设备上下文 mdc->CreateCompatibleDC(&dc); CBitmap bitmap; //CBitmap继承自CGdiObject //其成员变量m_hObject是一个句柄 //A HANDLE containing the HBITMAP, HPALETTE, HRGN, HBRUSH, HPEN, or HFONT //attached to this object. //可以简单记为bitmap拥有HBITMAP类型句柄m_hObject,而该句柄需要使用 //Windows API的LoadImage获得 bitmap.m_hObject=(HBITMAP)::LoadImage(NULL,"b1.bmp",IMAGE_BITMAP,5 00,400,LR_LOADFROMFILE); mdc->SelectObject(bitmap); CRect rect; //取得客户区域大小 GetClientRect(&rect); //BitBlt()将位图贴到要显示的DC dc.BitBlt(0,0,rect.right,rect.bottom,mdc,0,0,SRCCOPY); //最后要记得在析构函数中释放mdc