特效编辑器开发手记3——保存纹理到plist Base64编码与Zip压缩(上源码)

  开发这玩意从头到尾也不过4-5天,几个月前就已经写好了,源码也早就在一个cocos2d-x群里面上传过了,就是一种惰性拖拖拉拉,或许是不知道该写些什么吧,拖到现在才把这最后一篇文章写上。(自己画的图片,虽然很业余,但是感觉还过得去啦)

 
 开发这个粒子编辑器,总共碰到三个问题:
  第一个是在使用CCParticleSystem时出现的,无法动态调整粒子数量的问题——特效编辑器开发手记1——令人蛋疼菊紧的Cocos2d-x动态改变粒子数 过了这么久,新版本说不定有些什么变化
  第二个是如何将一个调整好的粒子系统保存成plist格式,这个很简单,分析好格式之后写xml就可以了——特效编辑器开发手记2——cocos2d-x粒子系统的plist文件
  第三个是将纹理保存到plist中,这个做起来比较复杂,因为涉及到Zip压缩以及Base64编码,在翻阅了cocos2d-x和zlib源码(这里用的是1.2.5版本的,刚开始找最新版本的源码看,接口都不一样~~)以及相关资料后,并修改了cocos2d-x相关代码,才保存成功的,
 
  cocos2d-x中,我们可以把粒子保存到一个plist文件中,然后在使用的时候,直接加载就可以使用这个粒子效果,这个功能很酷,在官方自带的例子中,可以将纹理一起写入到plist文件中,这是个很不错的功能,如果他们是一个plist外加一个png,我总是会担心找不到的问题,你在移动文件的时候不得不小心谨慎,或者说是提心吊胆吧~如果png写入到plist里面的话,那就万事大吉了,当然,当很多种粒子采用同一个png的时候,我还是宁愿提心吊胆,把png单独放出来。
  在cocos2d-x的粒子系统加载plist文件里面,对于写在plist的纹理,它是这样解析的,先把他使用base64解码(存入的时候使用base64编码的原因是,把二进制的纹理该成字符串,方便存到plist中,而这一过程会使纹理占用的内存变大),然后使用了zlib进行解压
  处理Base64编码还好,但处理zlib压缩的时候,确实让我头疼了,因为参数问题,在查看zlib源码之后,凭着程序猿的第七感,猜中了这个魔数,参考的相关文档如下
下载地址http://ncu.dl.sourceforge.net/project/gnuwin32/zlib/1.2.3/zlib-1.2.3-src.zip
文档地址http://nchc.dl.sourceforge.net/project/gnuwin32/zlib/1.2.3/zlib-1.2.3-doc.zip

 

cocos2d-x用的是1.2.5的版本,在zlib官网没找到,最后再libpng的sourceforge下找到了,真不好找啊
http://nchc.dl.sourceforge.net/project/libpng/zlib/1.2.5/zlib-1.2.5.tar.bz2

 在cocos2d-x粒子系统中,使用base64Decode将纹理数据从plist里面解码出来之后,还要进行一次数据解压缩,在这里plist里面的纹理数据是通过gzip压缩过的,解压函数调用了cocos自己封装的一个函数:ccInflateMemory,从文件到纹理总共三步走 
1 //解码
2 int decodeLen = base64Decode((unsigned char*)textureData, (unsigned int)dataLen, &buffer);
3  
4 //解压
5 int deflatedLen = ZipUtils::ccInflateMemory(buffer, decodeLen, &deflated);
6  
7 //创建CCImage
8 image = new CCImage();
9 bool isOK = image->initWithImageData(deflated, deflatedLen);

我们关键看ZipUtils::ccInflateMemory函数里的内容,ccInflateMemory第1,2个参数传入压缩内容以及压缩内容的长度,第3个参数是输出解压内容的指针

1 int ZipUtils::ccInflateMemory(unsigned char *in, unsigned int inLength, unsigned char **out)
2 {
3     // 256k for hint
4     return ccInflateMemoryWithHint(in, inLength, out, 256 * 1024);
5 }
6  

上面直接调用了ccInflateMemoryWithHint函数,第四个参数表示输出缓冲区的大小,被设置为256kb

ccInflateMemoryWithHint调用ccInflateMemoryWithHint之后,判断了一下错误状态并返回解压内容的长度

 1 int ZipUtils::ccInflateMemoryWithHint(unsigned char *in, unsigned int inLength, unsigned char **out, unsigned int outLengthHint)
 2 {
 3     unsigned int outLength = 0;
 4     int err = ccInflateMemoryWithHint(in, inLength, out, &outLength, outLengthHint);
 5 
 6     if (err != Z_OK || *out == NULL) {
 7         if (err == Z_MEM_ERROR)
 8         {
 9             CCLOG("cocos2d: ZipUtils: Out of memory while decompressing map data!");
10         } else 
11         if (err == Z_VERSION_ERROR)
12         {
13             CCLOG("cocos2d: ZipUtils: Incompatible zlib version!");
14         } else 
15         if (err == Z_DATA_ERROR)
16         {
17             CCLOG("cocos2d: ZipUtils: Incorrect zlib compressed data!");
18         }
19         else
20         {
21             CCLOG("cocos2d: ZipUtils: Unknown error while decompressing map data!");
22         }
23 
24         delete[] *out;
25         *out = NULL;
26         outLength = 0;
27     }
28 
29     return outLength;
30 }

最终我们看到了ZipUtil最底下的一层调用,也就是直接对zlib库函数的调用

 1 int ZipUtils::ccInflateMemoryWithHint(unsigned char *in, unsigned int inLength, unsigned char **out, unsigned int *outLength, unsigned int outLenghtHint)
 2 {
 3     /* ret value */
 4     int err = Z_OK;
 5  
 6     int bufferSize = outLenghtHint;
 7     *out = new unsigned char[bufferSize];
 8  
 9     z_stream d_stream; /* decompression stream */    
10     d_stream.zalloc = (alloc_func)0;
11     d_stream.zfree = (free_func)0;
12     d_stream.opaque = (voidpf)0;
13  
14     d_stream.next_in  = in;
15     d_stream.avail_in = inLength;
16     d_stream.next_out = *out;
17     d_stream.avail_out = bufferSize;
18  
19     /* window size to hold 256k */
20     if( (err = inflateInit2(&d_stream, 15 + 32)) != Z_OK )
21         return err;
22  
23     for (;;) 
24     {
25         err = inflate(&d_stream, Z_NO_FLUSH);
26  
27         if (err == Z_STREAM_END)
28         {
29             break;
30         }
31  
32         switch (err) 
33         {
34         case Z_NEED_DICT:
35             err = Z_DATA_ERROR;
36         case Z_DATA_ERROR:
37         case Z_MEM_ERROR:
38             inflateEnd(&d_stream);
39             return err;
40         }
41  
42         // not enough memory ?
43         if (err != Z_STREAM_END) 
44         {
45             delete [] *out;
46             *out = new unsigned char[bufferSize * BUFFER_INC_FACTOR];
47  
48             /* not enough memory, ouch */
49             if (! *out ) 
50             {
51                 CCLOG("cocos2d: ZipUtils: realloc failed");
52                 inflateEnd(&d_stream);
53                 return Z_MEM_ERROR;
54             }
55  
56             d_stream.next_out = *out + bufferSize;
57             d_stream.avail_out = bufferSize;
58             bufferSize *= BUFFER_INC_FACTOR;
59         }
60     }
61  
62     *outLength = bufferSize - d_stream.avail_out;
63     err = inflateEnd(&d_stream);
64     return err;
65 }

在上面的函数中我们看到了一个结构体和几个函数,这里简单介绍一下介绍

 1 typedef struct z_stream_s {
 2     Bytef    *next_in;  /* next input byte */
 3     uInt     avail_in;  /* number of bytes available at next_in */
 4     uLong    total_in;  /* total nb of input bytes read so far */
 5 
 6     Bytef    *next_out; /* next output byte should be put there */
 7     uInt     avail_out; /* remaining free space at next_out */
 8     uLong    total_out; /* total nb of bytes output so far */
 9 
10     char     *msg;      /* last error message, NULL if no error */
11     struct internal_state FAR *state; /* not visible by applications */
12 
13     alloc_func zalloc;  /* used to allocate the internal state */
14     free_func  zfree;   /* used to free the internal state */
15     voidpf     opaque;  /* private data object passed to zalloc and zfree */
16 
17     int     data_type;  /* best guess about the data type: ascii or binary */
18     uLong   adler;      /* adler32 value of the uncompressed data */
19     uLong   reserved;   /* reserved for future use */
20 } z_stream ;
21 
22 typedef z_stream FAR * z_streamp;  
int inflateInit2 (z_streamp strm, int windowBits); 
    这里另一个版本的infalteInit,这里加上了一个额外的参数,z_streamp的next_in,avail_in,zalloc,zfree和opaque都必须初始化    windowBits参数是用力表示窗口大小的以2为底的对数,它在这个版本的值必须是8-15这个范围,当使用infalteInit时,默认是填入15,    如果要解压的内容使用了更大的窗口大小,那么后面的inflate函数会返回一个Z_DATA_ERROR,而不是扩展它的窗口大小    inflateInit2不做任何解压,它只修改了next_in和avail_in两个成员变量


This is another version of inflateInit with an extra parameter. The fields next_in, avail_in, zalloc, zfree and opaque must be initialized before by the caller.
The windowBits parameter is the base two logarithm of the maximum window size (the size of the history buffer). It should be in the range 8..15 for this version of the library. The default value is 15 if inflateInit is used instead. If a compressed stream with a larger window size is given as input, inflate() will return with the error code Z_DATA_ERROR instead of trying to allocate a larger window.
inflateInit2 returns Z_OK if success, Z_MEM_ERROR if there was not enough memory, Z_STREAM_ERROR if a parameter is invalid (such as a negative memLevel). msg is set to null if there is no error message. inflateInit2 does not perform any decompression apart from reading the zlib header if present: this will be done by inflate(). (So next_in and avail_in may be modified, but next_out and avail_out are unchanged.)
int inflate (z_streamp strm, int flush); 
inflate函数用于解压数据,当数据解压完成或者输出缓冲区不足时停止


inflate decompresses as much data as possible, and stops when the input buffer becomes empty or the output buffer becomes full. It may some introduce some output latency (reading input without producing any output) except when forced to flush.

The detailed semantics are as follows. inflate performs one or both of the following actions:

  • Decompress more input starting at next_in and update next_in and avail_in accordingly. If not all input can be processed (because there is not enough room in the output buffer), next_in is updated and processing will resume at this point for the next call of inflate().
  • Provide more output starting at next_out and update next_out and avail_out accordingly. inflate() provides as much output as possible, until there is no more input data or no more space in the output buffer (see below about the flush parameter).

Before the call of inflate(), the application should ensure that at least one of the actions is possible, by providing more input and/or consuming more output, and updating the next_* and avail_* values accordingly. The application can consume the uncompressed output when it wants, for example when the output buffer is full (avail_out == 0), or after each call of inflate(). If inflate returns Z_OK and with zero avail_out, it must be called again after making room in the output buffer because there might be more output pending.

If the parameter flush is set to Z_SYNC_FLUSH, inflate flushes as much output as possible to the output buffer. The flushing behavior of inflate is not specified for values of the flush parameter other than Z_SYNC_FLUSH and Z_FINISH, but the current implementation actually flushes as much output as possible anyway.

inflate() should normally be called until it returns Z_STREAM_END or an error. However if all decompression is to be performed in a single step (a single call of inflate), the parameter flush should be set to Z_FINISH. In this case all pending input is processed and all pending output is flushed ; avail_out must be large enough to hold all the uncompressed data. (The size of the uncompressed data may have been saved by the compressor for this purpose.) The next operation on this stream must be inflateEnd to deallocate the decompression state. The use of Z_FINISH is never required, but can be used to inform inflate that a faster routine may be used for the single inflate() call.

If a preset dictionary is needed at this point (see inflateSetDictionary below), inflate sets strm-adler to the adler32 checksum of the dictionary chosen by the compressor and returns Z_NEED_DICT ; otherwise it sets strm-> adler to the adler32 checksum of all output produced so far (that is, total_out bytes) and returns Z_OK, Z_STREAM_END or an error code as described below. At the end of the stream, inflate() checks that its computed adler32 checksum is equal to that saved by the compressor and returns Z_STREAM_END only if the checksum is correct.

inflate() returns Z_OK if some progress has been made (more input processed or more output produced), Z_STREAM_END if the end of the compressed data has been reached and all uncompressed output has been produced, Z_NEED_DICT if a preset dictionary is needed at this point, Z_DATA_ERROR if the input data was corrupted (input stream not conforming to the zlib format or incorrect adler32 checksum), Z_STREAM_ERROR if the stream structure was inconsistent (for example if next_in or next_out was NULL), Z_MEM_ERROR if there was not enough memory, Z_BUF_ERROR if no progress is possible or if there was not enough room in the output buffer when Z_FINISH is used. In the Z_DATA_ERROR case, the application may then call inflateSync to look for a good compression block.

int inflateEnd (z_streamp strm);All dynamically allocated data structures for this stream are freed. This function discards any unprocessed input and does not flush any pending output.

inflateEnd returns Z_OK if success, Z_STREAM_ERROR if the stream state was inconsistent. In the error case, msg may be set but then points to a static string (which must not be deallocated). 

在inflateInit2_的解压参数配置里面,找到了下面的代码,解压所设置的窗口大小!

 1 int ZEXPORT inflateInit2_(strm, windowBits, version, stream_size)
 2 z_streamp strm;
 3 int windowBits;
 4 const char *version;
 5 int stream_size;
 6 {
 7     struct inflate_state FAR *state;
 8 
 9     if (version == Z_NULL || version[0] != ZLIB_VERSION[0] ||
10         stream_size != (int)(sizeof(z_stream)))
11         return Z_VERSION_ERROR;
12     if (strm == Z_NULL) return Z_STREAM_ERROR;
13     strm->msg = Z_NULL;                 /* in case we return an error */
14     if (strm->zalloc == (alloc_func)0) {
15         strm->zalloc = zcalloc;
16         strm->opaque = (voidpf)0;
17     }
18     if (strm->zfree == (free_func)0) strm->zfree = zcfree;
19     state = (struct inflate_state FAR *)
20             ZALLOC(strm, 1, sizeof(struct inflate_state));
21     if (state == Z_NULL) return Z_MEM_ERROR;
22     Tracev((stderr, "inflate: allocated\n"));
23     strm->state = (struct internal_state FAR *)state;
24     if (windowBits < 0) {
25         state->wrap = 0;
26         windowBits = -windowBits;
27     }
28     else {
29         state->wrap = (windowBits >> 4) + 1;
30 #ifdef GUNZIP
31         if (windowBits < 48) windowBits &= 15;
32 #endif
33     }
34     if (windowBits < 8 || windowBits > 15) {
35         ZFREE(strm, state);
36         strm->state = Z_NULL;
37         return Z_STREAM_ERROR;
38     }
39     state->wbits = (unsigned)windowBits;
40     state->window = Z_NULL;
41     return inflateReset(strm);
42 }

于是依葫芦画瓢,在cocos2d-x的ZipUtils类中加了一个函数,ccDeflateMemory

 1 int ZipUtils::ccDeflateMemory(unsigned char *in, unsigned int inLength, unsigned char **out)
 2 {
 3     int err = Z_OK;
 4     int outLength = 0;
 5     int bufferSize = 256 * 1024;
 6     *out = new unsigned char[bufferSize];
 7 
 8     z_stream d_stream; /* decompression stream */    
 9     d_stream.zalloc = (alloc_func)0;
10     d_stream.zfree = (free_func)0;
11     d_stream.opaque = (voidpf)0;
12 
13     d_stream.next_in  = in;
14     d_stream.avail_in = inLength;
15     d_stream.next_out = *out;
16     d_stream.avail_out = bufferSize;
17 
18     if( (err = deflateInit2(&d_stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 15 + 16, 8, Z_DEFAULT_STRATEGY)) != Z_OK )
19         return err;
20 
21     for (;;) 
22     {
23         err = deflate(&d_stream, Z_FINISH);
24 
25         if (err == Z_STREAM_END)
26         {
27             break;
28         }
29     }
30 
31     outLength = bufferSize - d_stream.avail_out;
32     err = deflateEnd(&d_stream);
33 
34     return outLength;
35 }

在程序读取纹理设置到粒子系统的时候,我对纹理进行了压缩拷贝,这样在保存到plist的时候,就可以很轻松地将纹理提取出来了,详情请看CCParticleSystem::setTexture(修改过的),zlib的源码不多,大概几千行,在网上找了一下关于zlib的文章,没找到什么经典,大部分是copy来copy去,但在一个论坛找到一篇帖子,是讲压缩原理的,还蛮不错的,文章很长,不是一次性说完的,后面LZ有很多回复,与大家分享一下 http://bbs.blueidea.com/thread-1819267-1-1.html

刚刚说到了Base64,在这里也简单介绍一下吧,这方面的资料其实很多,在博客园也有几篇很不错的文章,关于Base64,这篇文章个人觉得比较浅显易懂,也分享一下http://www.cnblogs.com/hongru/archive/2012/01/14/2321397.html

cocos2d-x自己写了一个Base64解码,没有编码的,本来想自己写一个编码函数,但发现那个xml类里面有实现好的,可以直接将内容传入,以base64编码存到xml中,就直接拿来用了

cocos2d-x的Base64
  1 namespace cocos2d {
  2 
  3 unsigned char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  4 
  5 int _base64Decode( unsigned char *input, unsigned int input_len, unsigned char *output, unsigned int *output_len );
  6 
  7 int _base64Decode( unsigned char *input, unsigned int input_len, unsigned char *output, unsigned int *output_len )
  8 {
  9     static char inalphabet[256], decoder[256];
 10     int i, bits, c = 0, char_count, errors = 0;
 11     unsigned int input_idx = 0;
 12     unsigned int output_idx = 0;
 13 
 14     for (i = (sizeof alphabet) - 1; i >= 0 ; i--) {
 15         //该字符在字符表中
 16         inalphabet[alphabet[i]] = 1;
 17         //字符对应的编码
 18         decoder[alphabet[i]] = i;
 19     }
 20 
 21     char_count = 0;
 22     bits = 0;
 23     //遍历整个base64字符串进行解码
 24     for( input_idx=0; input_idx < input_len ; input_idx++ ) {
 25         //获取字符
 26         c = input[ input_idx ];
 27         //如果字符是'='表示到达结尾
 28         if (c == '=')
 29             break;
 30         //如果字符不在编码表中,则跳过
 31         if (c > 255 || ! inalphabet[c])
 32             continue;
 33 
 34         //进行还原每四个字符还原为3个字节的数据
 35         bits += decoder[c];
 36         char_count++;
 37         if (char_count == 4) {
 38             //此时bits的有效字节为24位
 39             //取bits前面8位
 40             output[ output_idx++ ] = (bits >> 16);
 41             //取bits中间8位
 42             output[ output_idx++ ] = ((bits >> 8) & 0xff);
 43             //取bits最后8位
 44             output[ output_idx++ ] = ( bits & 0xff);
 45             bits = 0;
 46             char_count = 0;
 47         } else {
 48             //共左移3次 16位
 49             bits <<= 6;
 50         }
 51     }
 52     
 53     if( c == '=' ) {
 54         switch (char_count) {
 55             case 1:
 56 #if (CC_TARGET_PLATFORM != CC_PLATFORM_BADA)
 57                 std::fprintf(stderr, "base64Decode: encoding incomplete: at least 2 bits missing");
 58 #endif
 59                 errors++;
 60                 break;
 61             case 2:
 62                 output[ output_idx++ ] = ( bits >> 10 );
 63                 break;
 64             case 3:
 65                 output[ output_idx++ ] = ( bits >> 16 );
 66                 output[ output_idx++ ] = (( bits >> 8 ) & 0xff);
 67                 break;
 68             }
 69     } else if ( input_idx < input_len ) {
 70         if (char_count) {
 71 #if (CC_TARGET_PLATFORM != CC_PLATFORM_BADA)
 72             std::fprintf(stderr, "base64 encoding incomplete: at least %d bits truncated",
 73                     ((4 - char_count) * 6));
 74 #endif
 75             errors++;
 76         }
 77     }
 78     
 79     *output_len = output_idx;
 80     return errors;
 81 }
 82 
 83 int base64Decode(unsigned char *in, unsigned int inLength, unsigned char **out)
 84 {
 85     unsigned int outLength = 0;
 86     
 87     //should be enough to store 6-bit buffers in 8-bit buffers
 88     *out = new unsigned char[(size_t)(inLength * 3.0f / 4.0f + 1)];
 89     if( *out ) {
 90         int ret = _base64Decode(in, inLength, *out, &outLength);
 91         
 92         if (ret > 0 )
 93         {
 94 #if (CC_TARGET_PLATFORM != CC_PLATFORM_BADA)
 95             printf("Base64Utils: error decoding");
 96 #endif
 97             delete [] *out;
 98             *out = NULL;            
 99             outLength = 0;
100         }
101     }
102     return outLength;
103 }
104 
105 }/

完整的加载纹理到plist过程,请看ParticleLayer.cpp的buttonCallback函数

这个粒子编辑器距离我想完成的特效编辑器,还很遥远,我希望它可以有多个粒子系统混合的特效,并且能够控制粒子系统的移动轨迹,让粒子系统加载进来后就自动移动,多个运动中的粒子系统肯定是要酷一些的。或许再加上可视化的动作编辑功能会更好一些,这段时间要开始研究u3d还有自己制作cocos2d-x的游戏,所以,暂时也没多少精力去写这个了,如果谁有时间的话,可以尝试一下,这时候刚把代码传到115网盘了,点击分享——弹出一个窗口,哥果断把115网盘卸载了

写到这里,我先找一下哪个网盘~,有兴趣的可以先留下邮箱

已分享至金山快盘 http://www.kuaipan.cn/file/id_89577079170925026.htm

 
 
 
 
 
posted @ 2012-12-05 22:31  深圳-宝爷  阅读(2845)  评论(2编辑  收藏  举报