上节讲到了cocos2d-x在iOS上解压绝大数的图片格式为CCImage,然后再转化为纹理格式。其中有一个列外就是Webp格式。今天我们来看看Windows上面的情况,Android上的情况和Windows是类似的,都是使用第三方库来解码图片。按照道理来讲,Android上是可以采用类似iOS的技术,使用Android的系统库来解码图片,而且Android在4.0版本上已经开始原生支持Webp格式了。但是Android的系统库是Java的,如果要传到cocos2d-x中需要通过Jni调用,也就增加了不必要的麻烦。在早期cocos2d-x读取assets资源有严重的性能问题,本人采用了这个方法获得了巨大的性能提升。新的cocos2d-x读取assets的资源利用了缓存加速,性能和这种迂回的方式相差不多了。
好我们来看看windows平台下的CCImage文件,如果你是Mac系统,在xcode工程中是看不到这个文件的,但是你可以使用Sublime打开源码目录下面的文件自行查看。在Windows下面,CCImage的文件组织有点特别。除了platform下面的CCImage.h的头文件,和win32下面的CCImage.cpp文件以外,在platform下面还有两个额外的文件也就是CCImageCommon_cpp.h和CCImageCommonWebp.cpp文件。第一个文件也是源文件,命名为.h是可以通过条件编译导入到CCImage.cpp文件中,至于Webp文件要单独列出来,是因为iOS上也要用到这个文件。因为其他格式的图片解码和Webp的流程差不多,我们使用Webp作为讲解。还记得那个判断函数吗?在Windows下面他变成了这个样子:
bool CCImage::initWithImageData(void * pData,
int nDataLen,
EImageFormat eFmt/* = eSrcFmtPng*/,
int nWidth/* = 0*/,
int nHeight/* = 0*/,
int nBitsPerComponent/* = 8*/)
{
bool bRet = false;
do
{
CC_BREAK_IF(! pData || nDataLen <= 0);
if (kFmtPng == eFmt)
{
bRet = _initWithPngData(pData, nDataLen);
break;
}
else if (kFmtJpg == eFmt)
{
bRet = _initWithJpgData(pData, nDataLen);
break;
}
else if (kFmtTiff == eFmt)
{
bRet = _initWithTiffData(pData, nDataLen);
break;
}
#if (CC_TARGET_PLATFORM != CC_PLATFORM_WINRT) && (CC_TARGET_PLATFORM != CC_PLATFORM_WP8)
else if (kFmtWebp == eFmt)
{
bRet = _initWithWebpData(pData, nDataLen);
break;
}
#endif
else if (kFmtRawData == eFmt)
{
bRet = _initWithRawData(pData, nDataLen, nWidth, nHeight, nBitsPerComponent, false);
break;
}
else
{
// if it is a png file buffer.
if (nDataLen > 8)
{
unsigned char* pHead = (unsigned char*)pData;
if ( pHead[0] == 0x89
&& pHead[1] == 0x50
&& pHead[2] == 0x4E
&& pHead[3] == 0x47
&& pHead[4] == 0x0D
&& pHead[5] == 0x0A
&& pHead[6] == 0x1A
&& pHead[7] == 0x0A)
{
bRet = _initWithPngData(pData, nDataLen);
break;
}
}
// if it is a tiff file buffer.
if (nDataLen > 2)
{
unsigned char* pHead = (unsigned char*)pData;
if ( (pHead[0] == 0x49 && pHead[1] == 0x49)
|| (pHead[0] == 0x4d && pHead[1] == 0x4d)
)
{
bRet = _initWithTiffData(pData, nDataLen);
break;
}
}
// if it is a jpeg file buffer.
if (nDataLen > 2)
{
unsigned char* pHead = (unsigned char*)pData;
if ( pHead[0] == 0xff
&& pHead[1] == 0xd8)
{
bRet = _initWithJpgData(pData, nDataLen);
break;
}
}
}
} while (0);
return bRet;
}
如果是Webp格式的文件就采用下面这个函数初始化了:
bool CCImage::_initWithWebpData(void*pData,intnDataLen)
{
bool bRet = false;
do
{
WebPDecoderConfig config;
if(WebPInitDecoderConfig(&config)==0)break;
if(WebPGetFeatures((uint8_t*)pData,nDataLen,&config.input)!=VP8_STATUS_OK)break;
if(config.input.width==0||config.input.height==0)break;
config.output.colorspace=MODE_RGBA;
m_nBitsPerComponent=8;
m_nWidth = config.input.width;
m_nHeight = config.input.height;
m_bHasAlpha = true;
int bufferSize = m_nWidth * m_nHeight * 4;
m_pData = new unsigned char[bufferSize];
config.output.u.RGBA.rgba=(uint8_t*)m_pData;
config.output.u.RGBA.stride=m_nWidth*4;
config.output.u.RGBA.size=bufferSize;
config.output.is_external_memory=1;
if(WebPDecode((uint8_t*)pData,nDataLen,&config)!=VP8_STATUS_OK)
{
delete[]m_pData;
m_pData=NULL;
break;
}
bRet=true;
}while(0);
return bRet;
}
这个方式很简单,采用Google提供的Webp库解码图片。当然也要包含其头文件和lib。
#if defined(__native_client__) || defined(EMSCRIPTEN)
// TODO(sbc): I'm pretty sure all platforms should be including
// webph headers in this way.
#include "webp/decode.h"
#else
#include "decode.h"
#endif
其他格式由其第三方库的使用方法不同而不同,就不再多说了。相对而已Webp是很简洁的。这便是cocos2d-x中解码图片所采用的方法。还有一点值得注意的是,CCImage将其拷贝构造函数声明为了私有方法,也就是说你不能利用CCImage来赋值,毕竟里面是一大块内存,使用赋值或者添加到容器中都是很愚蠢的行为。
CCImage还有几个比较有趣的功能,他提供了一个saveToFile的函数。我们知道CCImage可以从相当多的格式初始化,那个这个saveToFile就可以作为一个转化函数来使用了。它能将图片保存为png或者jpg的格式,注意这个函数默认是不保存透明的,如果需要可以将其第二个参数设置为true。你可以采用压缩的图片格式打包,然后在游戏第一次运行的时候将其保存为易于解码的格式,来达到游戏发布体积和加载速度上的平衡。CCImage就能简单的完成这个操作。
CCImage还有一个功能就是,它能从一个string初始化一个图片。另外IOS和Android还提供了一个initWithStringShadowStroke的功能。如果你要显示很有艺术感的字体,不妨用用这个函数。字体的渲染向来都是很恶心的内容,在Windows和Android上cocos2d-x采用了一个叫做BitmapDC的概念,再调用各自的平台特性来完成这个工作。在Android上是采用jni的方式调用系统的函数实现的。在iOS上直接采用了系统函数,没有借用BitmapDC这个概念。有一个问题就是,如果你在Android上开启了Debug模式,那么会输出相当多的JVM释放资源的信息,这是因为每一次信息改动都生成了一张显示图片,造成JVM频繁回收。这极大的影响了游戏性能。所以TTF文件使用在Android上是极其低效的,如果你要做聊天,这方面有很大的优化余地。