利用CImage类来生成精灵动画

CImage类是ATL和MFC共用的一个类,其头文件为atlimage.h,主要用于jpg、png、gif等格式图片文件的打开,显示与保存。在VS2010(VS2010支持中文变量名)和以上版本的MFC编程中,不需要将头文件包含进来。VC6.0、VS2008创建MFC程序要使用CImage类,必须先将头文件包含进来,可以包含在当前代码的CPP文件中,也可以包含在所属类的头文件中,不过最好还是包含在工程的stdafx.h文件中。CImage总共有39个成员函数,在我们这个精灵动画实例里只用到了其中几个。
使用CImage的一般过程:
(1)、添加头文件
打开应用程序的stdafx.h文件添加CImage类的包含文件:

#include <atlimage.h>

(2)、加载和处理png图片
定义一个CImage类对象,然后调用CImage::Load方法装载一个外部图像文件。
首先看下面这段代码

CImage image;
image.Load(L"D:\\用户目录\\Pictures\\example.jpg");
image.Draw(GetDC()->m_hDC,CRect(0,0,320,240));

CImage类支持以Load方法读取本地磁盘上的文件,并用Draw方法来显示图片。其中路径字符串前面的“L”是MFC的一个宏,表示L修饰的字符串是UNICODE串,是宽字符,告诉编译器后面字符串包括’/0’每个字符都用两个字节来存储。

这里写图片描述
这里写图片描述

这里写图片描述

PNG图片中携带每像素的颜色信息和透明度信息,而CImage类在绘制PNG图片时,发现当前透明度为0,则会自动将此点填充为纯白色,由此,我们把像素的alpha透明值代入计算。如果一个像素它的alpha分量为0,即该像素是透明的,则该像素的红、绿、蓝三个分量通过下面三行代码会变为(0,0,0),则最终该像素变成了(0,0,0,0)的形式,那通过前面的学习已经知道24位像素(0,0,0)是一个纯黑色,那在我们这个例子中png图片中本身的不透明纯黑色像素就是(0,0,0,255),即在于后面alpha值的区别。
若是一个全白色的像素且该像素不透明,通过3行代码仍然会保持不变,即(255,255,255,255)。

pBits[index]=pBits[index]*pBits[index+3]/255;       //B
    pBits[index+1]=pBits[index+1]*pBits[index+3]/255;   //G
    pBits[index+2]=pBits[index+2]*pBits[index+3]/255;   //R

所以,在绘制半透明图片之前,先修改半透明图片的每个像素的RGB值。下面是加载图片和修改代码 (g_TransparentImg为半透明的CImage对象):

CImage g_TransparentImg;
CImage g_OpaqueImg;

bool LoadImages()
{
    g_TransparentImg.Load(L"bird.png");
    g_OpaqueImg.Load(L"sky.png");
    if(g_TransparentImg.IsNull()==true||g_OpaqueImg.IsNull()==true)
    {
        assert(!"failed of loading images");
        return false;
    }
    //判断源图片是否带有alpha值
    if (g_TransparentImg.GetBPP()==32)
    {
        BYTE* pBits=(BYTE*)g_TransparentImg.GetBits();
        if(g_TransparentImg.GetPitch()<0)   //如果
            pBits+=(g_TransparentImg.GetHeight()-1)*g_TransparentImg.GetPitch();
        for (int iy=0;iy<g_TransparentImg.GetHeight();iy++)
        {
            for (int ix=0;ix<g_TransparentImg.GetWidth();ix++)
            {
                int index=iy*abs(g_TransparentImg.GetPitch())+ix*4;
                pBits[index]=pBits[index]*pBits[index+3]/255;       //B
                pBits[index+1]=pBits[index+1]*pBits[index+3]/255;   //G
                pBits[index+2]=pBits[index+2]*pBits[index+3]/255;   //R
            }
        }
    }

    return true;
}

http://msdn.microsoft.com/zh-cn/library/h0ctk27z(v=vs.80).aspx

可以看到:

CImage::GetBPP() 函数原型:int GetBPP( ) Return Value:The number of bits
per pixel. The bits per pixel is usually 1, 4, 8, 16, 24, or 32。
CImage:: GetBits () 函数原型: void* GetBits( )
作用:GetBits()函数读取图片数据区,返回图片左上角像素(原点在左上角)或左下角像素(原点在左下角)的地址,跟图片内部顺序有关。
CImage::GetPitch 函数原型:int GetPitch( )
作用:如果图像的像素存储顺序是从上到下(也就是GetBits()返回左上角像素的地址),这时GetPitch()返回一个正值,反之为负值,大小为图像宽所占有的字节数,例如24位800*600的图片,返回值应该是正或负的800*3。这样用每一行的字节数乘行数就可以得到起始位置了。

pBits+=(g_TransparentImg.GetHeight()-1)*g_TransparentImg.GetPitch();
//该行代码得到图片的起始位置, 
``![这里写图片描述](http://img.blog.csdn.net/20161012190737774)`


BYTE是一个数据类型,8个位,即1个字节,其实是无符号字符型,可以把它用在所有需要用无符号字符型的任何地方。BYTE是让人关注它的长度,而不需要关注它的类型。其实一切都是byteshort2byteint 长度为4bytesizeof()就是获取数据类型是几个byte,因此在内存操作中都习惯使用byte作为最小存储单位。
(2)、读取精灵列表生成动画

void DrawImages(HDC hdc)
{
int frames[][2]={
{0,0}, {240,0}, {480,0}, {720,0}, {960,0},
{0,314},{240,314},{480,314},{720,314},{960,314},
{0,628},{240,628},{480,628},{720,628},{960,628},
{0,942},{240,942},{480,942},{720,942},{960,942},
{0,1256},{240,1256}
};
if(g_ OpaqueImg.IsNull()==false)
g_ OpaqueImg.Draw(hdc,0,0);
if(g_ TransparentImg.IsNull()==false)
g_ TransparentImg.Draw(hdc,x,y,240,314,
frames[fIndex][0],
frames[fIndex][1],
240,314);
//精灵帧每张间隔(精灵的移动速度)
tPre = GetTickCount();
x-=15;
//精灵飞出屏幕外后,将其重置到画布最右边
if(x<=-240)
{
x=1023;
}
//计算下一帧
fIndex++;
//若动画已经播放到最后一帧,则重新开始
if(fIndex>=21)
{
fIndex = 0;
}
}


首先我们用一个二维数组以像素为单位初始化了精灵列表中每张精灵帧所在png图片中的具体位置,

> CImage::IsNull 函数原型:bool IsNull( ) 若图片加载成功,返回False,如果当前没有成功加载,则范围True。
> CImage::Draw 函数原型: BOOL Draw(HDC hDestDC, int xDest, int yDest, int
> nDestWidth, int nDestHeight,   int xSrc, int ySrc, int nSrcWidth, int
> nSrcHeight )  BOOL Draw(HDC hDestDC, const RECT& rectDest, const RECT&
> rectSrc )  BOOL Draw(HDC hDestDC, int xDest, int yDest )  BOOL
> Draw(HDC hDestDC, const POINT& pointDest )  BOOL Draw(HDC hDestDC,int
> xDest, int yDest, int nDestWidth, int nDestHeight) BOOL Draw(HDC
> hDestDC, const RECT& rectDest )  其中,hDestDC用来指定绘制的目标设备环境句柄,(xDest,
> yDest)和pointDest用来指定图像显示的位置,这个位置和源图像的左上角点相对应。nDestWidth和nDestHeight分别指定图像要显示的高度和宽度,xSrc、ySrc、nSrcWidth和nSrcHeight用来指定要显示的源图像的某个部分所在的位置和大小。rectDest和rectSrc分别用来指定目标设备环境上和源图像所要显示的某个部分的位置和大小。

需要说明的是,Draw方法综合了StretchBlt()、TransparentBlt()和AlphaBlend()函数的功能。默认时,Draw的功能和StretchBlt()相同。但当图像含有透明色或Alpha通道时,它的功能又和TransparentBlt、AlphaBlend相同。因此,在一般情况下,我们都应该尽量调用CImage::Draw方法来绘制图像。通过追踪调试,可得知本例的CImage在绘制透明图像时,底层最终会调用AlphaBlend()函数,而AlphaBlend()函数执行的融合操作为:
Dst.RGB = Src.Alpha * Src.RGB + (1 - Src.Alpha) * Dst.RGB
即目标像素=源像素+(1-源像素Alpha值)*目标像素值
则一个透明度为0的(0,0,0,0)的像素与一个目标像素为(0.5,0.2,0.1)融合结果为(0.5,0.2,0.1),即目标像素保持不变,实现了透明效果。黑色像素(0,0,0,1)与该像素(0,0,0,1),即黑色像素实现了它的完全不透明效果,将会覆盖在目标像素之上。如果黑色像素透明度为0.5,则最终结果像素会是源像素和目标像素各50%的合成。

g_ TransparentImg .Draw(hdc,x,y,240,314,
frames[fIndex][0],
frames[fIndex][1],
240,314);//绘制

Draw()函数根据当前fIndex的值去读取精灵列表中22帧的其中一帧来绘制到当前的设备上下文中。

while( msg.message!=WM_QUIT )
{
if( PeekMessage( &msg, NULL, 0,0 ,PM_REMOVE) )
{
TranslateMessage( &msg );
DispatchMessage( &msg );
}
else
{
tNow = GetTickCount();
if(tNow-tPre >= 100)
DrawImages(hdc);
}
}
“`

上面的代码表示,只要程序没有退出,每次程序刷新屏幕窗口,就会调用一次自定义函数DrawImages(),上面的代码DrawImages()也显示它是调用Draw()函数来实现最终的绘图操作的,这就相当于每次程序更新屏幕窗口时,Draw()函数就得以执行一次,索引值fIndex也加一,即进一步读取下一帧。当读取到精灵列表最后一帧时,则将精灵帧索引值重置为0,表示要从精灵列表的第一张重新开始读起,如此不断重复循环,就实现了精灵动画效果。
DrawImages()中的tPre = GetTickCount();是获得操作系统启动到执行这个函数时的毫秒值,而在_tWinMain()里我们还执行了一次这个函数,如果两次执行这个函数的时间差为100毫秒,才执行DrawImages()函数,这就实现了延时控速。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值