特效編輯器開發手記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

 
 
 
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM