




已阅读5页,还剩11页未读, 继续免费阅读
版权说明:本文档由用户提供并上传,收益归属内容提供方,若内容存在侵权,请进行举报或认领
文档简介
使用标准使用标准 GDI 实现游戏品质的动画系统实现游戏品质的动画系统 燕良燕良 2002 年年 1 月月 / 前言前言2 GDI 基础基础.3 绘制一个位图(BITMAP)对象3 常用像素格式4 WINDOWS 下的基本动画系统下的基本动画系统4 动画驱动方式4 播放动画5 消除闪烁6 透明色(COLOR KEY)处理 7 ALPHA混合 .9 读取 JPEG,GIF 文件10 子窗口管理12 进阶技巧进阶技巧-使用使用 DIB.14 像素操作14 RLE 压缩.15 参考参考15 华山论键15 其它类库16 前言前言 说到实现游戏品质的动画,很多人会立刻想到 DirectX,没错 DirectDraw 很强大,但 是并不是必须用 DirectDraw 才行。动画后面的理论和技巧都是一样的,这和末端使用什么 API 没有太大关系(如果那 API 不是太慢的话)。就笔者实现的 NewImage Lib 的测试结果, 内部所有像素数据的存储和运算都纯软件实现,最后一步输出到屏幕使用 GDI 的性能比 DirectDraw 低不到 10%,在 Window9X 系统上要低 20%左右,这对很多软件来说是绝对可 以接受的。 现在应用程序界面越做越华丽,除了支持 SKIN 外,很多人都想在程序中加入一些例 如 sprite 动画这种原本用在游戏上的技术,因为这原因引入 DirectX API,显然是不值得的 (况且 DX 版本升级频繁,DX8 中已经用 DirectGraphic 取代了 DirectDraw)。本文将以笔者 使用标准 GDI 函数实现的商业游戏为例,带你进入高品质 2D 动画编程领域,并且保证其 设备无关性。 本文假设读者有 C/C+语言知识,Windows 编程基础,GDI 基本概念。下面我将主要 讲述我在过去工作中积累的经验和一些技巧,但是将不讲解以上基本概念。读者最好有 MFC 基础,本文给出的代码将主要使用 MFC,但是其中的道理却不限于 MFC。 GDI 基础基础 绘制一个位图绘制一个位图(Bitmap)对象对象 GDI 的所有操作都是在 DC(device context)上进行的,所以首先你应该有 DC 的概念,如 果你对 DC 还不了解,现在就去翻一翻 Windows 编程的书吧。 首先我们要 Load 一个 Bitmap 对象,使用 Win32 API 可以写成这样: /从资源从资源 Load 一个位图一个位图,如果从文件如果从文件 load 的话的话,可以使用可以使用:LoadImage() HBITMAP hbmp=:LoadBitmap(hInstance,MAKEINTRESOURCE(IDB_MYBMP); 如果使用 MFC 可以这样写: CBitmap bmp; Bmp.LoadBitmap(IDB_MYBMP); 想把这个位图对象绘制到窗口上就要先得到窗口的 DC,然后对这个 DC 操作。请留 意创建 MemoryDC 的代码,后面会用到。 Win32 API 的版本: /假设位图大小为假设位图大小为 100*100 像素像素 /假设假设 hwnd 是要绘制的窗口的是要绘制的窗口的 HANDLE HDC hwnddc=:GetDC(hwnd); HDC memdc=:CreateCompatibleDC(hwnddc); HBITMAP oldbmp=:SelectObject(memdc,hbmp); :BitBlt(hwnddc,0,0,100,100,memdc,0,0,SRCCOPY); if(oldbmp) :SelectObject(memdc,oldbmp); DeleteDC(memdc); :ReleaseDC(hwnd,hwnddc); MFC 版本: /假设是在一个假设是在一个 CWnd 派生类的成员函数中派生类的成员函数中 CClientDC dc(this); CDC memdc; memdc.CreateCompatibleDC( CBitmap *oldbmp=memdc.SelectObject( dc.BitBlt(0,0,100,100, if(oldbmp) memdc.SelectObject(oldbmp); 也可以这样: CClientDC dc(this); dc.DrawState(CPoint(0,0),CSize(100,100), 基本的代码就是这样,当然有更多的 API 可以用,这就要看你自己的了。 常用像素格式常用像素格式 要进行图像编程的化对像素格式不了解似乎说不过去。我想应该有较多的人并不太了解, 所以这里简要的介绍一下。 1.8bit 也叫做 256 色模式。每个像素占一个字节, 使用调色板。调色板实际上是一个颜色 表,简单的讲就是,我们有 256 个油漆桶(因为像素的取值范围是 0 到 255),每个油漆 桶里面漆的颜色都由红,绿,蓝(RGB)三中基本的油漆按不同比例配置而成。所以我 们指定一个像素的颜色的时候只需要指定它用的第几号桶就好了。 这种模式造就了 DOS 时代的神奇模式13H(320*200*256 色),因为 320*200*1Byte 正好是 16bit 指针寻址能力的范围。这种模式有 2 的 18 次方种颜色(通过改变调色板实 现),可以同时显示 256 中颜色。这模式刚刚推出的时候,有人惊呼这是人类智慧的结 晶呢!也是这种模式造就了 1992 年 WestWood 的和 1995 年大宇资 讯的这样的经典游戏。 在 Windows 下硬件调色板应该极少用到,但是你可以用软件调色板来压缩你的动画, 这也是在 2D 游戏中常用的技巧。 2.16bit 这也是笔者最喜欢的模式。它不使用调色板。每个像素占两个字节,存储 RGB 值。 我觉得这种像素格式的效果(同时显示颜色数)和存储量(也影响速度)取得了比较好的统 一。但是如果你是写应用程序的话,我劝你不要用它。因为它的 RGB 值都不是整个 BYTE,例如 565 模式(16bit 的一种模式),它的 RGB 所占用的 bit 就是这样的: RRRR RGGG GGGB BBBB 3.24bit 每个像素有三个 BYTE,分别存储 RGB 值,这对你来说是不是很方便?是不是太好 了?可惜对我们可怜的计算机却不是,因为 CPU 访问奇数的地址会很费劲,而且在硬 件工艺上也有很多困难(具体我也不太清楚,请做过硬件的高手指点),所以你会发现 你的显卡不支持这种模式,但是你可以在自己的软件中使用。 4.32bit 每个像素 4 个 BYTE,分别存储 RGBA,A 值就是 Alpha,也就是透明度,可以用 像素混合算法实现多种效果,后面你就会看到。 Windows 下的基本动画系统下的基本动画系统 动画驱动方式动画驱动方式 先略说一下动画的基本原理,程序播放动画一般过程都是: 绘制擦除绘制,这样的 重复过程,只要你重复的够快,至少每秒 16 次(被称作 16FPS,Frame per Second),我们可 怜的眼睛就分辨不出单帧的图像了,看上去就是动画了。 在 Windows 环境下要驱动这样重复不停的操作有两种方法: 1.设置 Timer 这很简单,只要设置一个足够短的 Timer,然后响应 WM_TIME(对应 MFC 中的 OnTimer 函数)就可以满足绝大部分应用程序的需要。缺点是不够精确,而且 Win2000 和 Win9x 系统的精确性又有较大差异。 2.在消息循环中执行动画操作 这是在游戏中常用的方法,一般都会把 WinMain 中的消息循环写成这样: while( TRUE ) / Look for messages, if none are found then / update the state and display it if( PeekMessage( TranslateMessage( DispatchMessage( else if( g_bActive )/在主窗口不激活时不更新,以节省资源在主窗口不激活时不更新,以节省资源 /执行动画更新操作执行动画更新操作 / Make sure we go to sleep if we have nothing else to do else WaitMessage(); 如果你使用 MFC,则需要重载 CWinAPP 的 Run 虚函数,把上述消息循环替换进去。 播放动画播放动画 现在我们有了一个适当的时机执行更新操作了,现在就让我们试试动画吧。下面的代 码将不再提供 Win32 的版本。 为了叙述方便,我需要一个播放动画的窗口,它必须是一个 CWnd 的派生类,假设这 个类叫做 CMyView,我们将在这个窗口中绘制动画。首先我们为这个类添加一个成员函数” void CMyView:RenderView()”,你可以使用上面提到的方法调用这个函数。 现在准备工作都做好了,我们的动画该怎么存储呢?别提动画 GIF89a 格式(如果你觉得 只有 GIF 才有动画的话,那我劝你去做美术好了,别干程序了),如果你只想要个简单的动 画播放当然可以,但是如果你想要做复杂点的,交互式动画,我劝你还是别用那东西。假 设我们有一个 4 帧的动画,怎么存储它呢?我首先想到的就是存 4 个 BMP 文件,然后读入 到一个 CBitmap 对象数组中,但是尊敬的大师 Scott Meyers 警告我们不要使用多态数组, 因为编译器在某些情况下不能准确计算数组中对象的大小,所以下标运算符会产生可怕的 效果。然后我就想到了用 CBitmap 指针数组,这到是不错,不过管理起来稍嫌麻烦。现在 看看我最终的解决方法吧。把一个帧序列安顺序拼接成一个文件,象这样: 然后用它创建一个 CImageList 对象,让我们仔细看一下创建的方法,使用 BOOL CImageList:Create( int cx, int cy, UINT nFlags, int nInitial, int nGrow ); 函数,前面两个参数用来指定我们一帧动画的尺寸。这样就创建了一个空的 ImageList,这 样做的好处是可扩展行比较强。下面我们需要把那个帧序列文件 Load 到一个 CBitmap 对 象中,你可以存成 JPG 或者 GIF 文件来节省容量(后面将提到读取这些文件的简单方法, 并且附一个实用类)。当我们有了一个合适的 CBitmap 对象后,可以把他添加到我们的 ImageList 中,使用: BOOL CImageList:int Add( CBitmap* pbmImage, COLORREF crMask ); 一个实例: const int SPRIRT_WIDTH=32; const int SPIRIT_HEIGHT=32; . m_myimglist.Create(SPIRIT_WIDTH,SPIRIT_HIGHT,ILC_COLOR24|ILC_MASK,1,1); if(bmp.Load(“myani.bmp”) m_myimglist.Add( 好了,现在我们已经准备好了这些数据,让我们来实作渲染函数吧,下面这端代码可以 循环播放上面的 4 帧动画,并且支持透明色(如果你不知道这个名字,稍后有讲解)哦! void CMyView:RenderView() CclientDC dc(this); Static int curframe=0; m_myimglist.Draw( curframe+; If(curframe m_myimglist.GetImageCount() Curframe=0; 上面这个代码没有写擦除的操作,因为这根据具体需要有较大不同。如果你只有 一个精灵动画的话,你可以用一个 Bitmap 对象保存精灵所占矩形区域的图像。你也可能需 要有一个大的背景图每帧都要更新(这里我不讨论象 dirty rect 这样的优化方法),所以你只 要每次都画背景,然后画精灵就好了。 怎么样?你已经实现了基本的动画系统,就是这么简单。 消除闪烁消除闪烁 如果你真正实现上面的代码的话,你会发现画面一闪一闪的,十分的不爽。 很多人都 会怪到 GDI 头上,他们又会骂 MS,说 GDI 太慢了。其实非也(不是指 MS 不该骂,呵呵), 任何直接写屏幕的操作都会产生闪烁,在 DOS 下直接写显存或者用 DirectDraw API 直接 写 Primary Surface 都会闪烁,因为你每个更新显示的操作都会被用户马上看到(因为垂直回 扫的原因, 或许会有延迟)。 消除闪烁最简单也是最经典的方法就是双缓冲(Double buffer)。所谓的双缓冲其实道理非 常简单,就是说我们在其它地方(简单的说就是不针对屏幕,不显示出来的地方)开辟一个 存储空间,我们把所有的动画都要渲染到这个地方,而不是直接渲染到屏幕上(针对屏幕的 存储区域)。在 GDI 中,直接针对屏幕就是窗口 DC, ”不可见的地方”一般可以用 Memory DC。在把所有动画渲染到后台缓冲之后,再一下次整体拷贝到屏幕缓冲区! 在纯软件 2D 图形引擎中,双缓冲一般意味着在内存中开辟一个区域用来存储像素数 据。而在 DirectDraw 中可以创建 Back Surface,在把所有动画渲染到 Back Suface 上之后, 然后使用 Flip 操作使其可见,Flip 操作因为只是设置可见 surface 的地址,所以非常快速。 让我们重写一下 void CMyView:RenderView()函数,来用 GDI 实现双缓冲: void CMyView:RenderView() CClientDC dc(this); CRect rc; GetClientRect(rc); CDC memdc; memdc.CreateCompatibleDC( CBitmap bmp; Bmp. CreateCompatibleBitmap ( CBitmap *oldbmp=memdc.SelectObject( Static int curframe=0; m_myimglist.Draw( curframe+; If(curframe m_myimglist.GetImageCount() Curframe=0; if(oldbmp) memdc.SelectObject(oldbmp); dc.BitBlt(0,0,rc.Width(),rc.Height(), 其中创建一个 Bitmap 对象,然后选入 Memory DC 是必须的,因为 CreateCompatibleDC 所创建的 DC 里面只含有一个 1*1 像素的单色 Bitmap 对象,所以如果 缺了这个步骤,任何在 MemoryDC 上的绘图操作都会没有效果。延伸出一个问题, CreateCompatibleBitmap 函数的第一个参数显然不可写成 if (m_hObject = NULL) return FALSE; CRect DRect; DRect=Rect(); DRect.OffsetRect(x,y); if(!pdc-RectVisible( COLORREF crOldBack=pdc-SetBkColor(RGB(255,255,255); COLORREF crOldText=pdc-SetTextColor(RGB(0,0,0); CDC dcimg,dctrans; if(dcimg.CreateCompatibleDC(pdc)!=TRUE) return FALSE; if(dctrans.CreateCompatibleDC(pdc)!=TRUE) return FALSE; CBitmap *oldbmpimg=dcimg.SelectObject(this); CBitmap bmptrans; if(bmptrans.CreateBitmap(Width(),Height(),1,1,NULL)!=TRUE) return FALSE; CBitmap *oldbmptrans=dctrans.SelectObject( dcimg.SetBkColor(mask); dctrans.BitBlt(0,0,Width(),Height(), pdc-BitBlt(x,y,Width(),Height(), pdc-BitBlt(x,y,Width(),Height(), pdc-BitBlt(x,y,Width(),Height(), if(oldbmpimg) dcimg.SelectObject(oldbmpimg); if(oldbmptrans) dctrans.SelectObject(oldbmptrans); pdc-SetBkColor(crOldBack); pdc-SetTextColor(crOldText); return TRUE; Alpha 混合混合 Alpha 混合是一种像素混合的方法。所谓的像素混合就是使用一定的算法把两个像素的 值混合成一个新的像素值(倒,和没说一样),通常我们都把两个像素的值,分别叫做源(src) 和目的(dst),然后把混合后的结果存入 dst 中: dst= src blend dst 如果源像素和目的像素都是 RGBA 格式,你可以使用每个像素的 Alpha 信息(或者叫做 Alpha 通道)组合出各种运算公式,例如 dst= src*src.alpha+dst*dst.alpha; 或者 dst=src*src.alpha + dst*(1-src.alpha)/这里我们假设这里我们假设 alpha 值是值是 01 的浮点数。的浮点数。 可惜标准 GDI 没有支持类似这种操作的函数(起码我没找到),它只支持另一种 Alpha 混合, 我把它叫做 const alpha blend,也就是把两幅都不包含 Alpha 通道的图像的按照一个固定的 Alpha 值混合到一起,也就是每个像素都使用同一 Alpha 值。GDI 的支持这个操作的函数 是: AlphaBlend(AlphaBlend( HDCHDC hdcDest, , intint nXOriginDest, , intint nYOriginDest, , intint nWidthDest, , intint hHeightDest, HDCHDC hdcSrc, , intint nXOriginSrc, , intint nYOriginSrc, , intint nWidthSrc, , intint nHeightSrc, , BLENDFUNCTIONBLENDFUNCTION blendFunction );); 这个 API 的参数个数略多了一些,但是我想其中的位置参数你可以轻松搞定,还有就 是源 DC 和目的 DC,当然了,我们的 GDI 只能对 DC 操作,而不是对我们的像素数据, 而我们只要把我的位图 select 到 DC 中就 OK 了,最后一个参数是一个结构,是用来指定 Alpha 的运算方式的,请看一个实际的例子: BLENDFUNCTION bf; bf.AlphaFormat=0; bf.BlendFlags=0; bf.BlendOp=AC_SRC_OVER; bf.SourceConstantAlpha=100;/指明透明度指明透明度,取值范围是取值范围是 0255 AlphaBlend(pdc-GetSafeHdc(),rc.left,rc.top,rc.Width(),rc.Height(), memdc.GetSafeHdc(),0,0,rc.Width(),rc.Height(),bf); 也许你看过很多游戏,在弹出文字对话框的时候都是在游戏画面上蒙一层半透明 的黑色,然后在这上面印字。使用上述操作就可以达到此效果。你可以先建立一个 Memory DC,然后把他填充为黑,然后把 Alpha 值设为 128,然后混合到你要绘制的 DC 上(不一定是窗口 DC 哦,记得我们前面将的双缓冲吗?)就 OK 了。 读取读取 JPEG,GIF 文件文件 JPEG 压缩算法综合的信号学和视觉心理学,而 GIF 格式,特别是支持动画的 GIF89a 格式为了节约容量也做了很多种非常变态的优化,所以要写一个完全支持这些标准 格式的解码器相当困难,也没有必要。 如果你需要进行 JPEG 文件的读写我推荐你使用 Intel Jpeg Lib,速度相当令人满 意。而 GIF 由于授权问题,没有任何官方组织提供的读写代码。 如果你只是需要读入 JPEG 和静态 GIF(或者只一帧的动态 GIF),我推荐你使用 Windows 提供的 OleLoadPicture 函数,下面这段代码可以把一个 JPG,GIF,BMP 读入到 Bitmap 对象中: BOOL CIJLBitmap:Load(LPCTSTR lpszPathName) BOOL bSuccess = FALSE; /Free up any resource we may currently have DeleteObject(); /open the file CFile f; if (!f.Open(lpszPathName, CFile:modeRead) TRACE(_T(“Failed to open file %s, Error:%xn“), lpszPathName, :GetLastError(); return FALSE; /get the file size DWORD dwFileSize = f.GetLength(); /Allocate memory based on file size LPVOID pvData = NULL; HGLOBAL hGlobal = GlobalAlloc(GMEM_MOVEABLE, dwFileSize); if (hGlobal = NULL) TRACE(_T(“Failed to allocate memory for file %s, Error:%xn“), lpszPathName, :GetLastError(); return FALSE; pvData = GlobalLock(hGlobal); /ASSERT(pvData); if(pvData=NULL) TRACE(_T(“Failed to lock memoryrn“); return FALSE; / read file and store in global memory if (f.Read(pvData, dwFileSize) != dwFileSize) TRACE(_T(“Failed to read in image date from file %s, Error:%xn“), lpszPathName, :GetLastError(); GlobalUnlock(hGlobal); GlobalFree(hGlobal); return FALSE; /Tidy up the memory and close the file handle GlobalUnlock(hGlobal); /create IStream* from global memory LPSTREAM pStream = NULL; if (FAILED(CreateStreamOnHGlobal(hGlobal, TRUE, GlobalFree(hGlobal); return FALSE; / Create IPicture from image file if (SUCCEEDED(:OleLoadPicture(pStream, dwFileSize, FALSE, IID_IPicture, (LPVOID*) if (SUCCEEDED(m_pPicture-get_Type( OLE_HANDLE hPalette; if (SUCCEEDED(m_pPicture-get_Handle( m_Palette.Attach(HPALETTE) hPalette); bSuccess = TRUE; /Free up the IStream* interface pStream-Release(); return bSuccess; 这个 class 的完整代码请看文章最后的参考。 子窗口管理子窗口管理 你也许注意过几乎所有游戏界面中的窗口都是使用动画的从屏幕外飞出(而且是半透明的, 这你已经可以做到了)。游戏中一般都使用自己的 UI 系统。这里我们可以借助 Windows 对 窗口的管理来轻松实现各种动画子窗口。 首先让我们从最简单的开始。假设在我们的动画窗口中需要一个漂亮的按钮怎么办,我 劝你最好不要使用 CBitmapButton,因为你已经上了每秒重画窗口 16 次以上这条贼船,我 建议你在每次重画父窗口的时候重画所有子窗口,如此一来子窗口上如果要求有动画操作, 也可以轻松实现了。既然做了,就把它做到最好。 那我们怎么定义一个 button 呢?你也许想到自己定义一个矩形区域,然后在父窗口的消 息响应函数中检测是否是对此区域操作,这样在重画父窗口的时候特殊的画一次这个矩形 区域就好了。这样是可以实现,但是显然不符合我们的 OOP 精神,界面元素一多,你很可 能就会乱了阵脚。最后的解决方法当然是使用我们可爱的 CWnd 类,显然所有的界面元素 都可以作为一个 CWnd 派生类的对象。不过我建议你不要从 CButton 派生,这带来的麻烦 远多于它的价值。从 CWnd 派生一个类,然后在 Create 时注意使用 WS_CHILD 风格,并 且指定父窗口为我们的动画窗口。 下面一个问题是如何调用这些子窗口重画操作呢?第一种较好的解决方法是先建立这样 一个虚基类: CmyAniWnd :public CWnd virtual void Render(CDC *pdc)=0; 假设你有一个 Button 类和一个 TextBox 类: CmyButton : public CmyAniWnd CmyTextBox: public CmyAniWnd 这两个类都必须实现 Render 函数,这样在父窗口类中你可以保存一个指针数组,例如 这样: CPtrArray m_allchild; 在创建一个 Button 时这样写: CmyButton *pbtn=new CmyButton; m_allchild.Add(pbtn); pbtn-Create(); 然后在我们父窗口的 RenderView 函数(前面提到的,每次更新调用)中这样写即可: CmyAniWnd *pchild=NULL; for(int I;I(m_allchild.GetAt(i); ASSERT(:IsWindow(pchild-GetSafeHwnd(); pchild-Render( 这是一个典型的虚函数的应用,在调用这些子窗口的 Render 函数时,我们不需要知道 它到底是 Button 还是 TextBox,虚函数机制会自动帮我们找到该调用的函数。还有一点就 是,请注意,一定要把子窗口渲染到我们的后台缓冲,也就是 Memory DC 中,否则还是 会闪烁的。 上面这种方法适合于子窗口数目固定,更高级的界面会要求触发某个事件的时候产生 一个子窗口,子窗口不断更新自己,并且在适当的时候把自己从 UI 系统中去除。让每个 子窗口管理自己的生命期,是个不错的主意,不是吗?那你最好不要使用上面保存指针数组 的方法,那样的话,子窗口在杀死自己的时候还要通知父窗口,以让父窗口把它的指针从 数组中移除,这显然具有很高的偶合性,不是我们想要的。因为我们的所有子窗口都是标 准的 Windows 对象,所以这使得我们有使用 Windows 消息的机会。我们首先要枚举所有 子窗口,然后发一个自定义的更新消息给它,并把我们的 MemoryDC 的指针作为参数,具 体例子代码如下: void CMyView:RenderView() /其它更新操作其它更新操作 :EnumChildWindows(GetSafeHwnd(),CMyView:UpdateChildWnd,LPARAM( /其它更新操作其它更新操作 其中第二个参数是一个回调函数,你必须把它声明成全局函数,或者类的 static 成员 函数,这里我们使用了后者。 BOOL CALLBACK CMyWnd:UpdateChildWnd(HWND hwnd, LPARAM lParam/*CDC* */) :SendMessage(hwnd,WM_COMMAND,CHILDCMD_RENDER,lParam); return TRUE; 这里我没有使用自定义消息,而是发送标准的 WM_COMMAND,这样你可以给那个 CmyAniWnd 虚基类添加一个 CWnd 虚函数 OnCommand(),然后在那里面检测如果 wParam 是 CHILDCMD_RENDER 的话,就调用纯虚函数 Render(以 lParam 作为参数),子 窗口派生类只要实现自己的 Render 函数就好,其它不用管了。 这里还有一个要注意的问题就是绘制的顺序问题,如果你想让子窗口盖住某些动画, 就应该先渲染那些动画,然后渲染子窗口,反之亦反。 进阶技巧进阶技巧-使用使用 DIB 像素操作像素操作 以上所有操作都局限于标准 GDI 函数,如果我们要实现更进一步的操作,例如 当傍晚你希望把整个画面的颜色渲染能淡红色调,晚上的时候你要把整个画面变暗,早上 再把它恢复到原来的亮度这些 GDI 都无法帮你做到。如果想达到上述效果,就必须 自己对像素的 RGB 值进行操作。 首先让我们要得到一个 Bitmap 对象中的像素数据。让我们看一下具体该怎么操作。假 设我们有一个 mybmp 是一个 CBitmap 对象(或者其派生类对象),下面的代码把 CBitmap 中 的像素取出: BITMAP bm; mybmp.GetBitmap( BITMAPINFO binfo; ZeroMemory( binfo
温馨提示
- 1. 本站所有资源如无特殊说明,都需要本地电脑安装OFFICE2007和PDF阅读器。图纸软件为CAD,CAXA,PROE,UG,SolidWorks等.压缩文件请下载最新的WinRAR软件解压。
- 2. 本站的文档不包含任何第三方提供的附件图纸等,如果需要附件,请联系上传者。文件的所有权益归上传用户所有。
- 3. 本站RAR压缩包中若带图纸,网页内容里面会有图纸预览,若没有图纸预览就没有图纸。
- 4. 未经权益所有人同意不得将文件中的内容挪作商业或盈利用途。
- 5. 人人文库网仅提供信息存储空间,仅对用户上传内容的表现方式做保护处理,对用户上传分享的文档内容本身不做任何修改或编辑,并不能对任何下载内容负责。
- 6. 下载文件中如有侵权或不适当内容,请与我们联系,我们立即纠正。
- 7. 本站不保证下载资源的准确性、安全性和完整性, 同时也不承担用户因使用这些下载资源对自己和他人造成任何形式的伤害或损失。
最新文档
- 气管技能考试题及答案
- 民法中考试题及答案
- 2025年广州市天河区五一小学教师招聘考试笔试试题(含答案)
- 北京知识付费主播培训课件
- 医学综合考试题(附参考答案)
- 压力性损伤诊疗与护理规范理论考核试题及答案
- 医用Ⅲ类射线装置试题及答案
- 各种注射技术操作并发症预防及处理试题(有答案)
- 2025年高压电工(复审)电工作业模拟考试题及答案
- 2024年税务师题库及答案(典优)
- 人教版初中英语试讲稿逐字稿66篇
- 手术器械保养与维护
- 《灌区数字孪生平台与-四预-功能建设》
- DB45T 2656-2023 仫佬族民居规范
- 沪教牛津版九上英语期末复习-专题02 Units 1~8 语法精讲
- 住宅物业安全隐患巡查制度
- 劳保用品验收标准
- 2024年北师大版八年级上册全册数学单元测试题含答案
- 军工PCB市场潜力
- 智能机器人售后服务流程预案
- 医院“安康杯”竞赛活动实施方案2
评论
0/150
提交评论