首页 > 代码库 > cocos2dx中怎样把texture保存为pvr或者pvr.ccz格式的文件

cocos2dx中怎样把texture保存为pvr或者pvr.ccz格式的文件

本文给大家介绍下在cocos2dx中怎样把texture保存为pvr或者pvr.ccz格式的文件 

  pvr格式的数据在IOS上直接交给显卡渲染的,而cocos2dx 中的texture是直接交给显卡渲染的,所以理论上将pvr格式的数据可以不进行任何数据的转换就可以生成一张texture,事实上确实是这样的。

一, 保存为pvr格式

    要保存pvr文件,我们首先分析cocos2dx引擎中是怎样解析pvr文件的。可以看到cocos2dx库中的CCTexturePVR类提供了两个方法来解析pvr格式图片分别是unpackPVRv2Data、unpackPVRv3Data,两个方法分别解析的是PVR的v2和v3版本。本文只分析v2版本:

bool CCTexturePVR::unpackPVRv2Data(unsigned char* data, unsigned int len)//data是直接从文件里读出来没有做任何处理的数据{    bool success = false;    ccPVRv2TexHeader *header = NULL;    unsigned int flags, pvrTag;    unsigned int dataLength = 0, dataOffset = 0, dataSize = 0;    unsigned int blockSize = 0, widthBlocks = 0, heightBlocks = 0;    unsigned int width = 0, height = 0, bpp = 4;    unsigned char *bytes = NULL;    unsigned int formatFlags;        // 用header指向data数据的头部(PVR v2格式的头部固定是52个字节,也就是data的前52个字节的数据,data剩下的数据就是生成texture的数据了)    header = (ccPVRv2TexHeader *)data;    // 这是头部的一个标识值为 "PVR!" 占4字节    pvrTag = CC_SWAP_INT32_LITTLE_TO_HOST(header->pvrTag);    if (gPVRTexIdentifier[0] != (char)(((pvrTag >>  0) & 0xff)) ||        gPVRTexIdentifier[1] != (char)(((pvrTag >>  8) & 0xff)) ||        gPVRTexIdentifier[2] != (char)(((pvrTag >> 16) & 0xff)) ||        gPVRTexIdentifier[3] != (char)(((pvrTag >> 24) & 0xff)))    {        return false;    }        CCConfiguration *configuration = CCConfiguration::sharedConfiguration();    flags = CC_SWAP_INT32_LITTLE_TO_HOST(header->flags);    formatFlags = flags & PVR_TEXTURE_FLAG_TYPE_MASK;    bool flipped = (flags & kPVR2TextureFlagVerticalFlip) ? true : false;    if (flipped)    {        CCLOG("cocos2d: WARNING: Image is flipped. Regenerate it using PVRTexTool");    }    if (! configuration->supportsNPOT() &&        (header->width != ccNextPOT(header->width) || header->height != ccNextPOT(header->height)))    {        CCLOG("cocos2d: ERROR: Loading an NPOT texture (%dx%d) but is not supported on this device", header->width, header->height);        return false;    }        unsigned int pvr2TableElements = PVR2_MAX_TABLE_ELEMENTS;    if (! CCConfiguration::sharedConfiguration()->supportsPVRTC())    {        pvr2TableElements = 9;    }    for (unsigned int i = 0; i < pvr2TableElements; i++)    {        //Does image format in table fits to the one parsed from header?        if (v2_pixel_formathash[i].pixelFormat == formatFlags)        {            m_pPixelFormatInfo = v2_pixel_formathash[i].pixelFormatInfo;                        //Reset num of mipmaps            m_uNumberOfMipmaps = 0;            //Get size of mipmap            m_uWidth = width = CC_SWAP_INT32_LITTLE_TO_HOST(header->width);            m_uHeight = height = CC_SWAP_INT32_LITTLE_TO_HOST(header->height);                        //Do we use alpha ?            if (CC_SWAP_INT32_LITTLE_TO_HOST(header->bitmaskAlpha))            {                m_bHasAlpha = true;            }            else            {                m_bHasAlpha = false;            }                        //Get ptr to where data starts..            dataLength = CC_SWAP_INT32_LITTLE_TO_HOST(header->dataLength);            //跳过头部,bytes直接指向了图片数据部分            bytes = ((unsigned char *)data) + sizeof(ccPVRv2TexHeader);            m_eFormat = m_pPixelFormatInfo->ccPixelFormat;            bpp = m_pPixelFormatInfo->bpp;                        // Calculate the data size for each texture level and respect the minimum number of blocks            while (dataOffset < dataLength)            {                switch (formatFlags) {                    case kPVR2TexturePixelFormat_PVRTC_2BPP_RGBA:                        blockSize = 8 * 4; // Pixel by pixel block size for 2bpp                        widthBlocks = width / 8;                        heightBlocks = height / 4;                        break;                    case kPVR2TexturePixelFormat_PVRTC_4BPP_RGBA:                        blockSize = 4 * 4; // Pixel by pixel block size for 4bpp                        widthBlocks = width / 4;                        heightBlocks = height / 4;                        break;                    case kPVR2TexturePixelFormat_BGRA_8888:                        if (CCConfiguration::sharedConfiguration()->supportsBGRA8888() == false)                         {                            CCLOG("cocos2d: TexturePVR. BGRA8888 not supported on this device");                            return false;                        }                    default:                        blockSize = 1;                        widthBlocks = width;                        heightBlocks = height;                        break;                }                                // Clamp to minimum number of blocks                if (widthBlocks < 2)                {                    widthBlocks = 2;                }                if (heightBlocks < 2)                {                    heightBlocks = 2;                }                dataSize = widthBlocks * heightBlocks * ((blockSize  * bpp) / 8);                unsigned int packetLength = (dataLength - dataOffset);                packetLength = packetLength > dataSize ? dataSize : packetLength;                                // bytes指向的是data,而data是直接从文件里面读出来的数据,m_asMipmaps则是用来生成texture的数据,                // 所以这里可以得出结论,pvr的图片数据到texture不需要经过任何转换,                // 也就是说pvr格式的数据可以不进行任何数据的转换就可以生成一张texture                m_asMipmaps[m_uNumberOfMipmaps].address = bytes + dataOffset;                m_asMipmaps[m_uNumberOfMipmaps].len = packetLength;                m_uNumberOfMipmaps++;                                //Check that we didn‘t overflow                CCAssert(m_uNumberOfMipmaps < CC_PVRMIPMAP_MAX,                          "TexturePVR: Maximum number of mipmaps reached. Increase the CC_PVRMIPMAP_MAX value");                                dataOffset += packetLength;                                //Update width and height to the next lower power of two                 width = MAX(width >> 1, 1);                height = MAX(height >> 1, 1);            }                        //Mark pass as success            success = true;            break;        }

 

  通过上面的分析,我们知道了要把texture保存成pvr格式的文件只需为这样texture添加一个头部即可。关于pvr头部在CCTexturePVR.cpp中我们可以看到 _PVRTexHeader 这个结构体,这个结构体就是PVR的头部格式,现在要做的工作就是分析出_PVRTexHeader的成员的含义。这个可以参考imageination关于pvr的文档,需要提醒的是要注意版本差别。这里截出了头部描述的部分:

     根据表格的解析,PVR的头部就比较容易写出了。

  具体步骤: 1. 设置头部数据

        2. 把头部数据和texture数据合并

        3. 输出

  这是我在CCImage中添加的保存PVR格式的方法,大家可以参考下:

bool CCImage::_saveImageToPVRCCZ(const char *pszFilePath){    bool bRet = false;    do     {        CC_BREAK_IF(NULL == pszFilePath);        unsigned int *pvrHeader = new unsigned int[52]; // pvr header V2
pvrHeader[0] = 52; //headerLength pvrHeader[1] = m_nHeight; // height 变量是CCImage中的成员变量 pvrHeader[2] = m_nWidth; //width 变量是CCImage中的成员变量 pvrHeader[3] = 0; //numMipmaps pvrHeader[4] = 0x8012;//flags pvrHeader[5] = 0; //dataLength pvrHeader[6] = 0x20; // bpp = 32 // R G B A 是连续的4个字节,每个自街上都赋值为0xff表明存在活使用该颜色值(如果不使用A通道直接把A通道的值置为0x0即可) //0xff ff ff ff pvrHeader[7] = 0xff; // bitmaskRed pvrHeader[8] = 0xff00; // bitmaskGreen G pvrHeader[9] = 0xff0000; // bitmaskBlue B pvrHeader[10] = 0xff000000; // bitmaskAlpha A pvrHeader[11] = 0x21525650; // pvrTag = PVR! pvrHeader[12] = 1; // numSurfs unsigned int blockSize = 1; // default pvrHeader[5] = m_nWidth * m_nHeight * ((blockSize * 32) / 8); unsigned long pCombineDataLength = m_nWidth * m_nHeight * 4 + 52; unsigned char *pCombineData = http://www.mamicode.com/new unsigned char[pCombineDataLength]; // header memcpy(pCombineData, (unsigned char *)pvrHeader, 52); CC_SAFE_DELETE_ARRAY(pvrHeader); // body memcpy(pCombineData + 52, m_pData, m_nWidth * m_nHeight * 4); //m_pData就是用来渲染纹理的数据(pvr除了头部的那部分数据) m_bPreMulti = true; // pCombineData就是一个完成的pvr格式的数据了,在此处输出pCombineData即可 if (!ret) { CCLOG("cocos2d: CompressPvrToCCZFile failed!"); CC_SAFE_DELETE_ARRAY(pCombineData); return false; } CC_SAFE_DELETE_ARRAY(pCombineData); bRet = true; } while (0); return bRet;}

 

二,保存为pvr.ccz格式

  那么现在来分析保存为PVR.CCZ格式。我们可以跟踪一张pvr.ccz格式的图片,观察其是怎样生成texture的,最终发现在CCTexturePVR类的initWithContentsOfFile方法中可以看到这段语句

    if (lowerCase.find(".ccz") != std::string::npos)    {        pvrlen = ZipUtils::ccInflateCCZFile(path, &pvrdata);    }

  除此之外pvr.ccz 和pvr格式的处理过程是一模一样的。ccInflateCCZFile相当于解压了pvr.ccz文件并把解压后的数据保存到了pvrdata中。所以要把pvr转成pvr.ccz我们需要先把pvr数据用ZipUtils库压缩,再为这个数据添加一个头部即可。ZipUtils.h定义了一个CCZHeader,这个就是pvr.ccz的头部格式,pvr.ccz的头部比较简单,只有4个字段,这4个字段的数据都可以从ccInflateCCZFile中了解到,这里及不再赘述了,具体步骤如下:

  1. 先把pvr数据压缩,利用ZipUtils的compress方法压缩后得到压缩后的数据outBuffer

  2. 为outBuffer添加一个头部:
    CCZHeader header = {{‘C‘, ‘C‘, ‘Z‘, ‘!‘}, CCZ_COMPRESSION_ZLIB , 256, 0, CC_SWAP_INT32_BIG_TO_HOST(inLength)};
    注意:inLength是pvr数据的长度,这里必须转换为大端的表示方法。(在ccInflateCCZFile中可以看到把这个长度转换为小端)

  3. 把两个数据一次输出到同一个文件即可。代码如下(此处参考了http://www.cnblogs.com/howeho/p/3586379.html):

bool ZipUtils::ccCompressPvrToCCZFile(unsigned char *inBuffer,unsigned long inLength, const char *pszFileName)// inBuffer是pvr数据,inLength是pvr数据的长度{    bool bRet = false;    do     {        if (NULL == pszFileName) {            CCLOG("cocos2d: Error pszFileName NULL!");        }        if(NULL == inBuffer || 0 == inLength)        {            CCLOG("cocos2d: Error argument inBuffer or inLength");            return false;        }        unsigned long bufferSize = inLength;        char* outBuffer=new char[(uInt)bufferSize];        memset(outBuffer, 0, bufferSize);                int ret = compress((Bytef*)outBuffer,(uLongf*)&bufferSize,(const Bytef*)inBuffer,(uLongf)inLength);        if (ret != Z_OK) {            CCLOG("cocos2d: Failed to compress data");            CC_SAFE_DELETE_ARRAY(outBuffer);            return false;        }        CCZHeader header = {{C, C, Z, !}, CCZ_COMPRESSION_ZLIB , 256, 0, CC_SWAP_INT32_BIG_TO_HOST(inLength)};        // writeFileData 是我在CCFIleUtils中自己添加的写文件的方法,大家可以自己添加或者直接在此处输出        CCFileUtils::sharedFileUtils()->writeFileData(pszFileName, "w", (const char*)&header, sizeof(header));        CCFileUtils::sharedFileUtils()->writeFileData(pszFileName, "ab+", (const char*)outBuffer, bufferSize);        CC_SAFE_DELETE_ARRAY(outBuffer);        bRet = true;    } while (0);    return bRet;}

 

cocos2dx中怎样把texture保存为pvr或者pvr.ccz格式的文件