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 = 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;
}
时间: 2024-10-05 04:58:38

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

在Cocos2d-X中使用xml

XML即可扩展标记语言,在游戏开发中,常用于保存游戏信息,如最高分,游戏等级,等信息,和描述一些资源等,我第一次使用xml是在使用CCAnimation创建动画中,使用plist文件加载动画时,使用了xml文件其中plist文件其实就是一个xml文件,在前面的博客中的在Cocos2d-X中使用瓦片地图<一>和在Cocos2d-X中使用瓦片地图<二>中使用瓦片地图编辑器创建的瓦片地图保存后会得到一个tmx格式的文件,tmx文件也是一个xml文件 xml文件还可以解决中文乱码的问题,C

【转载】TexturePacker 如何使用自带的加密功能及在cocos2dx中的使用

在cocos2dx中使用纹理图集是非常节省资源的,在这里推荐 TexturePacker,而且 TexturePacker工具的加密接口也非常的好用,下面就来介绍一下... TexturePacker 工具的加密,只是相对于一般使用的基础上增加了几步对密码操作的步骤(目前的加密功能仅适用于.pvr.ccz格式): 一.生成密码(图形化界面和命令行两种方式) 方式一:图形化界面生成密码 密码可以自己手动过输入,或者使用下面的这几个按钮,完后点击外面的空白区域就自动保存了... (2)shell脚本

TexturePacker 如何使用自带的加密功能及在cocos2dx中的使用

在cocos2dx中使用纹理图集是非常节省资源的,在这里推荐 TexturePacker,而且 TexturePacker工具的加密接口也非常的好用,下面就来介绍一下... TexturePacker 工具的加密,只是相对于一般使用的基础上增加了几步对密码操作的步骤(目前的加密功能仅适用于.pvr.ccz格式): 一.生成密码(图形化界面和命令行两种方式) 方式一:图形化界面生成密码 密码可以自己手动过输入,或者使用下面的这几个按钮,完后点击外面的空白区域就自动保存了... (2)shell脚本

cocos2d-x中几种存储数据的方式

说明:本文所论述内容均基于cocos2dx 3.0 版本. 1.UserDefault 它是cocos2d-x用来存取基本数据类型用的.保存为XML文件格式. 查看CCUserDefault文件,可以看出,文件名默认为UserDefault.xml.在win32平台,Debug下,该文件在Debug.win32文件夹内.如果该文件不存在,则会创建新文件. 1 // root name of xml 2 #define USERDEFAULT_ROOT_NAME "userDefaultRoot&

在Cocos2d-x中使用SQLlite数据库

SQLite,是一款轻型的数据库,是遵守ACID的关系型数据库管理系统,它的设计目标是嵌入式的,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的低,在嵌入式设备中,可能只需要几百K的内存就够了.它能够支持Windows/Linux/Unix等等主流的操作系统,同时能够跟很多程序语言相结合,比如 Tcl.C#.PHP.Java等,还有ODBC接口' 在Cocos2d-X中使用SQLlite数据库 首先下载SQLlite数据库,SQLlite数据库的下载地址:http://download.

cocos2d-x中的简单的数据存储

Cocos2d-x中的数据存储方式很多,下面我来简单的介绍几种,有说的不准确的地方,大家指出来吧,共同学习进步.谢谢! 首先介绍的是  UserDefault 这个类: //用userdefault进行数据存储 UserDefault::getInstance()->setStringForKey("testKey","testValue");//将String类型的数据存储起来 //读取上面写入的数据 std::string val = UserDefaul

Cocos2d-X中的动作展示《二》

由于Cocos2d-X中的动作较多,我将所有的动作制作成了一个滚动视图,每个滚动视图上都有动作名,单击滚动视图就可以展示相应的动作 程序效果图: 使用滚动视图实现动作切换 动作展示 首先创建一个ActionMore类 ActionMore.h中的代码 #ifndef _ActionMore_H_ #define _ActionMore_H_ #include "cocos2d.h" #include "cocos-ext.h" USING_NS_CC; USING_

Cocos2d-X中实现菜单特效

Cocos2d-X中可以讲菜单和动作结合起来使用实现菜单特效 程序实例1:使用菜单和动作的组合实现菜单特效<一> #include "MenuItem.h" CCScene* MenuItem::scene() { CCScene* scene = CCScene::create(); MenuItem* layer = MenuItem::create(); scene->addChild(layer); return scene; } bool MenuItem:

cocos2d-x 中LUA和平台之间的函数调用理解

先看一张流程图如下: 第一步: 先把NDKHelper中的方法转成LUA中可以调用的,转得方法可参考quick中LUA的用法.这个类主要是中间桥梁的作用,它可以根据是什么平台调用IOSNDKHelper或者AndroidSNDKHelper,这些判断都是用C语言来写的.还有就是对一些回调函数的保存机制和平台要用LUA中一些方法的保存. 第二步: IOS平台需要处理的就是IOSNDKHelper,这个类主要就是接收和发送给NDKHelper数据的方法,还有一个就是加载IOS平台的BasePlatf