简、易-sachinKung

导航

[原创]cocos2dx加载网络图片&异步加载图片

  • 【动机】

   之前看到一款卡牌游戏,当你要看全屏高清卡牌的时候,游戏会单独从网络上下载,本地只存了非高清的,这样可以省点包大小,所以我萌生了实现一个读取网络图片的类。

  • 【联想】

之前浏览网页的时候经常看到一张图片渐进(由模糊变清晰)的显示,如果在游戏中,诸如像显示高清卡牌的时候,使用有这种方式去显示一张图片,这样的体验应该会稍微好些

  • 【相关知识】

  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

posted on 2014-06-17 21:07  sachinKung  阅读(3249)  评论(0编辑  收藏  举报