📄 专业tc--图形篇6.htm
字号:
supplied, use it.<BR>if (pClipRect) {kk1}<BR>rcDraw.IntersectRect(pClipRect,
&rcDraw);<BR>}<BR>// Draw the image of the DIB to the off-screen
buffer.<BR>ASSERT(m_pDIB);<BR>pDIB->CopyBits(m_pDIB, <BR>rcDraw.left,<BR>rcDraw.top, <BR>rcDraw.right
- rcDraw.left,<BR>rcDraw.bottom -
rcDraw.top,<BR>rcDraw.left, <BR>rcDraw.top);<BR>以上就是创建背景图像的基本过程。<BR><BR>在Windows应用程序中,如果想使用离屏位图制作子图形动画,就需要C++类来表示子图形和便于图形动起来。动画包含一个固定的背景图像,零个或多个能在背景上四处移动的子图形。下面各节定义了
Sprite 类用以构造子图形的模型,还定义了 SpriteAnimation
类来维护子图形和背景图像。<BR>Sprite类<BR>Sprite有两个Image对象:<BR>·黑色背景上的Sprite图像<BR>·白色背景上的Sprite图像的黑色轮廓像(掩膜)<BR>animate
函数是 SpriteAnimation 类不可少的函数,由这个函数可知,要做到绘制 Sprite
轮廓时丝毫不影响其背景图像和掩膜是必不可少的。除开图像和掩膜,Sprite
类还包括x,y位置,以及其他一些记录它在背景上的运行轨迹的变量。<BR>一个Sprite对象还有它自己的显示优先级。显示优先级用整型量表示,存储在成员变量disp_
priority中,它决定了绘制重叠子图形的次序——优先级高的子图形被绘制到优先级低的子图形的上面。<BR>另一个成员变量,DRAWPROC类型的dproc很有意思,它由下面的typedef语句声明:<BR>typedef
void (_FAR PASCAL *DRAWPROC) (HDC hdc,short x,short y,LFVOID data)<BR>可见,dproc
是指向函数的指针。在需要绘制 Sprite
图像的任何时候都会调用dproc指定的函数。您可以绘制对象——例如直线、矩形、椭圆或文本——这样一个子图形就可以有多个位图式图像,例如应用程序 ANIMATE
显示移动文本,就用到了 Sprite
的这个特性。<BR><BR>Sprite类继承了Borland类库(CLASSLIB)中的Sortable类,由于只有Sortable
对象才可以放在SortedArray(Borland CLASSLIB 的另一个类)中,继承后就可以把Sprite 对象存储在SortedArray
中。由于继承了Sortable ,Sprite 类必须定义以下的成员函数:<BR>classType isA ( ) const ;<BR>char_FAR *
nameof( ) const ;<BR>hashValueType hash Value( ) const ;<BR>void printOn
(ostream_FAR& os)
;<BR>这四个函数的定义都很简单。函数的isA返回唯一的整型标识符,表示Sprite类,函数nameOf返回类名,即字符串“Sprite”。<BR>此外,为了Sprite对象能够正确地排序,Sprite类必须包括下面的函数:<BR>int
isLessThan (const Object_FAR& ob ) const ;<BR>int isEqual(const
Object_FAR& ob ) const
;<BR>函数isLessThan用来检验某个Sprite是否“小于”另一个。正如程序所定义的那样,isLessThan通过比较子图形的是显示优先级来决定哪个子图形“小些”。<BR>初始化
Sprite<BR>创建和初始化Sprite的典型方法是使用构造函数,它以图像文件名和掩膜文件名为多数。<BR>Sprite::sprite (HDC
hdc,LPSTR imagefilename<BR>,LPSTR maskfilename,short
priority)<BR>该构造函数调用init_image函数装入相应的图像位图和掩膜位图。构造函数还要求有设备场景的句柄。这是因为图像位图和掩膜位图要转换为设备无关格式,这一步骤需要一个DC。<BR>函数init_image从文件中装入图像。它通过文件扩展名来判断图像类型。<BR>该函数接受下列扩展名:<BR>.BMP
即 Windows DIB 文件。<BR>.PCX 即 PC PaintBrush 文件。<BR>.TGA 即 24 位,Truevision Targa
文件。<BR><BR>上面介绍了如何生成背景图像,子图形的基本概念。这一节让我们看一下怎样创建一个或两个子图形,并把它们添加到场景中去。<BR>为何需要通知视图在场景中装入了一个新的子图形有两个原因:首先,子图形只有被画出来才能成为可见的,而第二,子图形中的颜色必须被映射到由屏幕视图缓冲区使用的调色板中。<BR>注意,即使背景和子图形使用的是同一个调色板,但由于考虑到系统颜色和调色板项的逻辑到物理映射之间的作用方式,仍然需要将子图形映射到屏幕缓冲区调色板上。屏幕缓冲区中的调色板是由背景DIB中的颜色表而创建的。当调色板成为一种等同调色板时(出于最好性能的考虑),可以对颜色进行重新排序,因此背景DIB和所有子图形中的象素必须被改变以具有在新的(等同)调色板中的正确索引。如果你没有将子图形的颜色映射到屏幕缓冲区中,那么显示出来的子图形颜色将都是错误的。<BR>首先,让我们看一看简单的CAnimView:
:NewSprite函数:<BR>void CAnimView::NewSprite(CSprite* pSprite)<BR>{ kk1}<BR>// Map
the colors in the sprite DIB to the<BR>// palette in the off-screen buffered
view.<BR>if (m_pPal)
{kk1}<BR>pSprite->MapColorsToPalette(m_pPal);<BR>}<BR><BR>// Render the scene
with the new sprite.<BR>Render();<BR><BR>// Draw the
result.<BR>Draw();<BR>}<BR>通过调用MapColorsToPalette函数,DIB的颜色被映射到调色板上。调用Render函数在屏幕缓冲区构画子图形图象,然后Draw函数将缓冲区拷贝到屏幕上,使子图形在场景中可见。Map
Colors To Palette函数和我们为CDIB类开发的函数几乎完全一样:<BR>BOOL
CSprite::MapColorsToPalette(CPalette* pPal)<BR>{kk1}<BR>BOOL bResult =
CDIB::MapColorsToPalette(pPal);<BR>// Get the transparency info
again.<BR>Initialize();<BR>return bResult;<BR>}<BR>事实上,你可以看到CDIB: :Map Colors To
Palette被调用来进行实际的映射,而CSprite类所特有的唯一工作是再次获取透明信息,这是因为我们使用的是调色板索引来定义子图形图象中的哪种颜色是透明的,因而需要在调用Map
Colors To Palette时被重新映射。<BR>需要对Render函数进行一点小的改进以处理在构画背景图象之外还得构画子图形。<BR>void
CAnimView::Render(CRect* pClipRect)<BR>{kk1}<BR>CAnimDoc* pDoc =
GetDocument();<BR>CRect rcDraw;<BR><BR>// Get the background DIB and render
it.<BR>CDIB* pDIB = pDoc->GetBackground();<BR>if (pDIB)
{kk1}<BR>pDIB->GetRect(&rcDraw);<BR>// If a clipping rectangle was
supplied, use it.<BR>if (pClipRect) {kk1}<BR>rcDraw.IntersectRect(pClipRect,
&rcDraw);<BR>}<BR><BR>// Draw the image of the DIB to the off-screen
buffer.<BR>ASSERT(m_pDIB);<BR>pDIB->CopyBits(m_pDIB, <BR>rcDraw.left,<BR>rcDraw.top, <BR>rcDraw.right
- rcDraw.left,<BR>rcDraw.bottom -
rcDraw.top,<BR>rcDraw.left, <BR>rcDraw.top);<BR>}<BR><BR>// Get the sprite
and render it.<BR>CSprite* pSprite = pDoc->GetSprite();<BR>if (pSprite)
{kk1}<BR>pSprite->Render(m_pDIB,
&rcDraw);<BR>}<BR>}<BR>背景图象被首先画到缓冲区。如果存在一个子图形,那么它也被画到屏幕缓冲区,位于背景之上。这就是本书中所说的“面向对象欺骗”。我们并没有书写大量的代码来将子图形的图象画到屏幕缓冲区,而只是让子图形自己来完成这一工作。这样作意义重大,因为这种作法使子图形的特有代码集中在子图形对象中,从而使我们可以将注意力放在功能上而不必纠缠在实现细节上。<BR>当然,还得我们来书写子图形的构图代码,因此让我们看一看下面的代码:<BR>void
CSprite::Render(CDIB* pDIB, CRect*
pClipRect)<BR>{kk1}<BR>ASSERT(pDIB);<BR><BR>// Get the sprite
rectangle.<BR>CRect rcDraw;<BR>GetRect(&rcDraw);<BR><BR>// If a clipping
rectangle was supplied, see if the sprite<BR>// is visible inside the
rectangle.<BR>if (pClipRect) {kk1}<BR>if (!rcDraw.IntersectRect(pClipRect,
&rcDraw)) {kk1}<BR>return; // Not visible<BR>}<BR>// Copy the image of the
sprite.<BR>CopyBits(pDIB, // Dest DIB<BR>rcDraw.left, // Dest x<BR>rcDraw.top,
// Dest y<BR>rcDraw.right - rcDraw.left,// Width<BR>rcDraw.bottom -
rcDraw.top,// Height<BR>rcDraw.left - m_x, // Source x<BR>rcDraw.top - m_y, //
Source y<BR>PALETTEINDEX(m_bTransIndex)); <BR>// Transparent color
index<BR>}<BR>当然我们需要检验子图形是否在裁剪板矩形中是可见的(如果提供了裁剪板矩形的话)。一旦计算出实际的绘图矩形后,CDIB::CopyBits函数被用来将子图形图像的非透明部分拷贝到屏幕缓冲区中。CDIB::CopyBits的透明特征是一种新的特性,请读者在动画设计时注意。<BR><BR>为了实现重画区域,需要将一个区域列表保存在某个位置,以及一种往列表中添加列表项的方法,而当要执行一次构图和画图动作时,需要一种遍历列表的方法。因为构图和画图主要涉及到COSBView类中的屏幕缓冲区,因此作者决定在COSBView中增加重画列表及其管理函数。<BR>首先来看一看应用程序的其余部分是如何使用重画列表的,然后再进一步讨论重画列表的函数是如何实现的。先看一看下面的这个代码,此代码实现了对ANIMVIEW.
CPP中一个子图形的鼠标拖动:<BR>void CAnimView::OnMouseMove(UINT nFlags, CPoint
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -