- 【動機】
之前看到一款卡牌游戲,當你要看全屏高清卡牌的時候,游戲會單獨從網絡上下載,本地只存了非高清的,這樣可以省點包大小,所以我萌生了實現一個讀取網絡圖片的類。
- 【聯想】
之前瀏覽網頁的時候經常看到一張圖片漸進(由模糊變清晰)的顯示,如果在游戲中,諸如像顯示高清卡牌的時候,使用有這種方式去顯示一張圖片,這樣的體驗應該會稍微好些
- 【相關知識】
png interlaced:png圖片在導出的時候是可以選擇 interlaced (Adam7)的,這樣的存儲的png在網頁上顯示會漸進顯示,
這種interlaced方式是由adam 開發的,分為7段掃描,具體方式如下面的gif圖
jpg progressive:在web瀏覽器上很多都是使用這種模式的圖片
- 【png解碼】
cocos2d-x沒有對interlaced模式進行支持,libpng本身肯定是支持的,對interlaced圖片png必須使用png_progressive_combine_row來逐行讀取,非interlaced的png圖片也是一樣支持的,libpng解析,首先我們要初始化png_structp,所有解析的信息都在這個結構體里
bool PNGCodec::PrepareDecode() { png_reader_.png_struct_ptr_= png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL); if (!png_reader_.png_struct_ptr_) return false; png_reader_.png_info_ptr_ = png_create_info_struct(png_reader_.png_struct_ptr_); if (!png_reader_.png_info_ptr_) { png_destroy_read_struct(&png_reader_.png_struct_ptr_, NULL, NULL); return false; } if (setjmp(png_jmpbuf(png_reader_.png_struct_ptr_))) { png_destroy_read_struct(&png_reader_.png_struct_ptr_, &png_reader_.png_info_ptr_, (png_infopp)NULL); return false; } png_set_error_fn(png_reader_.png_struct_ptr_, NULL, LogLibPNGDecodeError, LogLibPNGDecodeWarning); png_set_progressive_read_fn(png_reader_.png_struct_ptr_, &png_reader_, &DecodeInfoCallback, &DecodeRowCallback, &DecodeEndCallback); png_reader_.decode_state_ = PNGCodec::DecodeState::DECODE_READY; return true; }
這里主要是png_set_progressive_read_fn 函數,通過設置回調方式,第3個參數是讀完png_info(png頭)的回調,第4個參數row讀入的回調,第5個參數是解析結束的的回調
有這些回調函數,我們設置回調函數,通過回調函數來更新sprite的texture
/*
@parm1:png_structp @parm2:自定義指針 @parm3:void *png_progressive_info_ptr(png_struct* png_ptr, png_info* info_ptr) @parm4:void *png_progressive_row_ptr(png_struct* png_ptr, png_byte* new_row, png_uint_32 row_num, int pass) @parm5:void png_progressive_end_ptr(png_struct* png_ptr, png_info* info)
*/ void, png_set_progressive_read_fn(png_structrp png_ptr, png_voidp progressive_ptr, png_progressive_info_ptr info_fn, png_progressive_row_ptr row_fn, png_progressive_end_ptr end_fn))
- 【思路】
加載網絡圖片首先從網下下載png數據,通過curl把數據送給png解析,通過png的回調來更新sprite的textrue,把下載和解析放在一個線程里做,這樣就不會阻塞了
我實現了四個類
PNGCoder:主要完成對png圖片的解析
HttpConnection:對curl的封裝
CCInterlacedImage:用於緩存png解析后的數據
WebSprite: 主要提供initWithFileUrl接口,
用戶通過創建一個websprite:initWithFileUrl,並websprite加到scene中,由websprite來創建線程和創建httpconneciton,
- 【碰到的問題】
1.如何線程通信:之前使用boost庫的時候,boost 實現io_sevice,可以通過boost::asio io_service, io_sevice實際上是一個function隊列,他是線程安全的,
c++11我沒找到,所以我在websprite也創建了這樣的一個隊列,但是要自己去處理這個隊列的線程安全,這個可以通過鎖來實現
2.如何終止線程:當我們釋放websprite,線程屬於分離狀態,線程無法強轉終止,std:thread沒有提供相關接口,curl_easy_perform是阻塞的,當你要釋放websprite的時候,這個時候線程還在跑,怎么終止 curl可以通過size_twriteData(void*ptr,size_tsize,size_tnmemb,void*stream) 的返回0時,curl_easy_perform會終止返回錯誤,
3.如何處理內存釋放的問題:因為這是跨線程的,數據的安全釋放就要變得尤為小心,因為我的通常你可能需要設置某個標志位在兩個線程間來通知相關指針是否已經失效,使用共享指針線程之間的內存釋放問題可以很好的解決了,你不需要去關心這個問題,引用計數來解決這個問題,std:shared_ptr的引用計數是線程安全的
- 【效果圖】
這是在瀏覽
http://daltonclaybrook.com/future.png
- 【代碼】
#include "CCWebSprite.h" #include "CCInterlacedPngImage.h" #include "http_connection.h" #include "png_codec.h" #include <future> namespace cocos2d { // Callback function used by libcurl for collect response data size_t WebSprite::DataBridge::WriteData(void *ptr, size_t size, size_t nmemb, void *stream) { if (stream == nullptr) { return 0; } WebSprite* web_sprite = static_cast<WebSprite*>(stream); if (web_sprite == nullptr) { return 0; } size_t sizes = size * nmemb; web_sprite->reciverData((unsigned char*)ptr, sizes); return sizes; } void WebSprite::DataBridge::ReadHeaderCompleteCallBack(void* ptr) { WebSprite* web_sprite = static_cast<WebSprite*>(ptr); web_sprite->readHeaderComplete(); } void WebSprite::DataBridge::ReadRowCompleteCallBack(void* ptr, int pass) { WebSprite* web_sprite = static_cast<WebSprite*>(ptr); web_sprite->readRowComplete(pass); } void WebSprite::DataBridge::ReadAllCompleteCallBack(void* ptr) { WebSprite* web_sprite = static_cast<WebSprite*>(ptr); web_sprite->readAllComplete(); } WebSprite::WebSprite() : http_connection_(nullptr), png_coder_(std::make_shared<util::PNGCodec>()), interlaced_png_image_buff_(new InterlacedPngImage()), code_pass_(-1){ } WebSprite::~WebSprite() { if (http_connection_ != nullptr) { http_connection_->SetWriteCallBack(nullptr, WebSprite::DataBridge::WriteData); } png_coder_->SetReadCallBack(nullptr, nullptr, nullptr, nullptr); CC_SAFE_RELEASE(interlaced_png_image_buff_); } WebSprite* WebSprite::create() { WebSprite *sprite = new WebSprite(); if (sprite && sprite->init()) { sprite->autorelease(); return sprite; } CC_SAFE_DELETE(sprite); return nullptr; } WebSprite* WebSprite::createWithFileUrl(const char *file_url) { WebSprite *sprite = new WebSprite(); if (sprite && sprite->initWithFileUrl(file_url)) { sprite->autorelease(); return sprite; } CC_SAFE_DELETE(sprite); return nullptr; } bool WebSprite::initWithFileUrl(const char *file_url) { Sprite::init(); file_url_ = file_url; if (isRemotoeFileUrl(file_url)) { return initWithRemoteFile(); } else { return initWithLocalFile(); } } bool WebSprite::initWithRemoteFile() { assert(http_connection_ == nullptr); http_connection_ = std::make_shared<HttpConnection>(); http_connection_->Init(file_url_.c_str()); png_coder_->PrepareDecode(); png_coder_->SetReadCallBack(this, WebSprite::DataBridge::ReadHeaderCompleteCallBack, WebSprite::DataBridge::ReadRowCompleteCallBack, WebSprite::DataBridge::ReadAllCompleteCallBack); http_connection_->SetWriteCallBack(this, WebSprite::DataBridge::WriteData); this->scheduleUpdate(); std::thread http_thread = std::thread(std::bind(&HttpConnection::PerformGet, http_connection_)); http_thread.detach(); return true; } bool WebSprite::initWithLocalFile() { auto filePath = FileUtils::getInstance()->fullPathForFilename(file_url_); std::shared_ptr<Data> data = std::make_shared<Data>(FileUtils::getInstance()->getDataFromFile(filePath)); png_coder_->PrepareDecode(); png_coder_->SetReadCallBack(this, &WebSprite::DataBridge::ReadHeaderCompleteCallBack, WebSprite::DataBridge::ReadRowCompleteCallBack, WebSprite::DataBridge::ReadAllCompleteCallBack); std::thread http_thread = std::thread(std::bind([=](){ png_coder_->Decoding(data->getBytes(), data->getSize()); } )); http_thread.detach(); this->scheduleUpdate(); return true; } bool WebSprite::isRemotoeFileUrl(const char *file_url) { if (strlen(file_url) > 7 && (strncmp(file_url, "http://", 7) == 0)) { return true; } return false; } void WebSprite::reciverData(unsigned char* data, size_t data_size) { png_coder_->Decoding(data, data_size); } void WebSprite::updateTexture() { cocos2d::Texture2D* texture = cocos2d::Director::getInstance()->getTextureCache()->addImage(interlaced_png_image_buff_, file_url_); texture->updateWithData(interlaced_png_image_buff_->getData(), 0, 0, interlaced_png_image_buff_->getWidth(), interlaced_png_image_buff_->getHeight()); SpriteFrame* sprite_frame = cocos2d::SpriteFrame::createWithTexture(texture, CCRectMake(0,0,texture->getContentSize().width, texture->getContentSize().height)); Sprite::setSpriteFrame(sprite_frame); } void WebSprite::readHeaderComplete() { interlaced_png_image_buff_->setImageHeader(png_coder_->png_width(), png_coder_->png_height(), png_coder_->png_color_type(), png_coder_->png_output_channels()); } void WebSprite::readRowComplete(int pass) { if (code_pass_ < pass) { perform_mutex_.lock(); interlaced_png_image_buff_->setImageBodyData((char*)png_coder_->png_data_buffer(), png_coder_->png_data_size()); perform_main_thread_functions_.push_back(std::bind(&WebSprite::updateTexture, this)); perform_mutex_.unlock(); code_pass_ = pass; } } // run on sub thread void WebSprite::readAllComplete() { perform_mutex_.lock(); interlaced_png_image_buff_->setImageBodyData((char*)png_coder_->png_data_buffer(), png_coder_->png_data_size()); perform_main_thread_functions_.push_back(std::bind(&WebSprite::updateTexture, this)); perform_mutex_.unlock(); } void WebSprite::update(float fDelta) { Sprite::update(fDelta); perform_mutex_.lock(); for (std::vector<std::function<void ()> >::iterator it = perform_main_thread_functions_.begin(); it != perform_main_thread_functions_.end(); ++it) { (*it)(); } perform_main_thread_functions_.clear(); perform_mutex_.unlock(); }
這是在cocos2d3.0基礎上開發的,把下面的文件替換掉ccp-empty-test,就可以了
https://github.com/SachinKung/WebSprite
- 【參考】
1.https://github.com/daltonclaybrook/SFSInterlacedImageView
2.https://code.google.com/p/chromium/codesearch#chromium/src/ui/gfx/codec/png_codec.h&q=png_code&sq=package:chromium&l=1