Cocos2d-xとOpenCVで動画スプライト

[前回の記事](http://qiita.com/Hare8563/items/bae7ac731535215acd54)でOpenCVの問題でできなかったのがなんとかなりましたのでまとめます。

１、問題の解決法と設定
前回はcocos2d-xとopencvのlibpng等のライブラリのバージョンがあってないということで問題が生じてました。そこで、某掲示板にて質問したところ、

**ライブラリをダイナックリンクではなく静的リンクをさせろ**

とのことで、[このへん](http://blog.mogmet.com/cocos2dx-build-static-library-on-xcode-62-for-rapid-build/)を参考に静的リンクにしたところうまくいきました。

ただ一点よくわからないのが、XcodeのBuild Phasesの設定でLink BinaryWith Librariesの順番が逆だとうまくいかない点ですね。これ順番関係あったんだ。

![スクリーンショット 2015-06-14 17.12.14.png](https://qiita-image-store.s3.amazonaws.com/0/43429/51413fec-a88d-b120-10ab-579dc41a91d7.png)

２、コードの変更
opencvがうまく使えるようになったんでいろいろ前回のコードを見直し、メモリの消費をできるだけ少ないようにするように作り直しました。

```c++:VideoSprite.h
#include <iostream>
#include "cocos2d.h"
#include <opencv2/opencv.hpp>
#include <opencv2/highgui/highgui_c.h>

class VideoSprite : public cocos2d::Sprite
{
public:
    CREATE_FUNC(VideoSprite);
    static VideoSprite* create(const std::string& filename);
    virtual bool initWithFile(const std::string& filename);
    bool isPlaying()
    {
        return __playing;
    };
    void run(bool repeatFlag);
protected:
    ~VideoSprite()
    {
        if (currentTexture != NULL)
        {
            //currentTextureの確保したメモリの解放
            delete currentTexture;
        }
    };
    bool initWithClearColor();
    void setCaptureData(float delta);
private:
    cv::VideoCapture __capture;
    std::string currentFile;
    bool isRepeat=false;
    bool __playing=false;
    cocos2d::Texture2D *currentTexture;
};
```

```c++:VideoSprite.cpp
#include "VideoSprite.h"

VideoSprite* VideoSprite::create(const std::string & fileName)
{
    VideoSprite *sprite = new (std::nothrow) VideoSprite();
    if (sprite && sprite->initWithFile(fileName))
    {
        sprite->autorelease();
        return sprite;
    }
    CC_SAFE_DELETE(sprite);
    return nullptr;
}

//ファイル名でVideoSpriteを初期化させる
bool VideoSprite::initWithFile(const std::string& fileName)
{
    CCASSERT(fileName.size()>0, "Invalid filename for sprite");
    
    __capture = cv::VideoCapture(fileName);
    currentFile = fileName;
    
    //もし、VideoCaptureのopenに失敗したら
    if (!__capture.isOpened()) {
        std::cerr << "cvCreateFileCapure filed" << std::endl;
        return false;
    }
    
    //WHITEで初期化される
    return initWithClearColor();
}

//動画再生を実行するメソッド
//repeatFlag: 再生時にリピートを可能にするか
void VideoSprite::run(bool repeatFlag)
{
    isRepeat = repeatFlag;

    this->schedule(schedule_selector(VideoSprite::setCaptureData));
    __playing = true;
}

//スケジュールで定期的に呼び出されるメソッド
void VideoSprite::setCaptureData(float delta)
{
    //動画から1Frame分読み込む
    cv::Mat m;
     __capture >> m;

    if (m.empty()) {
        
        if (isRepeat == false) {
            //スケジュールを止めてこれ以上動画を読み込まない
            this->unschedule(schedule_selector(VideoSprite::setCaptureData));
            __playing = false;
            return;
        }
        else
        {
            //もしもrepeatフラグがあるならば
            //OpenCV iOSで__capture.setやgetがまともに動作しないため、サポートするまでこれで最初に戻る
            __capture = cv::VideoCapture(currentFile);
            __capture >> m;
        }
    }
    
    //mはBGRで格納されるのでRGBAへと変換させる
    cv::Mat dst;
    cv::cvtColor(m, dst, CV_BGR2RGBA);

    if (currentTexture==NULL) {
        //まだテクスチャを作ってない場合のみ作成する
        currentTexture = new cocos2d::Texture2D();
        //そのままだとRGBA8888と1px表現するのに4byte消費と多いため、RGB565の16bitへと圧縮させる
        //もし透過動画を使用したい場合は、ここをコメントアウトさせるか、PixelFormat::RGBA4444を使うのがいい
        currentTexture->setDefaultAlphaPixelFormat(cocos2d::Texture2D::PixelFormat::RGB565);
        setTexture(currentTexture);
    }


    //テクスチャの大きさを指定
    auto rect = new cocos2d::Rect();
    
    rect->setRect(0, 0, dst.cols, dst.rows);
    setTextureRect(*rect);
    
    //データでテクスチャを作成
    currentTexture->initWithData(dst.data, dst.channels()*dst.cols*dst.rows, cocos2d::Texture2D::PixelFormat::RGBA8888, dst.cols, dst.rows, cocos2d::Size(dst.cols, dst.rows));
    
    //不要になったRect型は削除する
    delete rect;
}


//テクスチャを初期化
//デフォルトのものとほとんど変わらないが、Textureはここでは設定しない
bool VideoSprite::initWithClearColor()
{
    bool result;
    if (Node::init())
    {
        _batchNode = nullptr;
        
        _recursiveDirty = false;
        setDirty(false);
        
        _opacityModifyRGB = true;
        
        _blendFunc = cocos2d::BlendFunc::ALPHA_PREMULTIPLIED;
        
        _flippedX = _flippedY = false;
        
        // default transform anchor: center
        setAnchorPoint(cocos2d::Vec2(0.5f, 0.5f));
        
        // zwoptex default values
        _offsetPosition.setZero();
        
        // clean the Quad
        memset(&_quad, 0, sizeof(_quad));
        
        // Atlas: Color
        _quad.bl.colors = cocos2d::Color4B::WHITE;
        _quad.br.colors = cocos2d::Color4B::WHITE;
        _quad.tl.colors = cocos2d::Color4B::WHITE;
        _quad.tr.colors = cocos2d::Color4B::WHITE;
        
        // shader state
        setGLProgramState(cocos2d::GLProgramState::getOrCreateWithGLProgramName(cocos2d::GLProgram::SHADER_NAME_POSITION_TEXTURE_COLOR_NO_MVP));
        
        // by default use "Self Render".
        // if the sprite is added to a batchnode, then it will automatically switch to "batchnode Render"
        setBatchNode(nullptr);
        result = true;
    }
    else
    {
        result = false;
    }
    _recursiveDirty = true;
    setDirty(true);
    return result;
}
```

前回の物と違い、今回作成し直した物はSprite型の継承になっているため、シーンにスプライトを配置するように使えるようになります。

使い方は

```c++:
//VideoSpriteの作成
auto vidSprite = VideoSprite::create();
//位置やスケールなども普通のスプライトのように使用可能
vidSprite->setPosition(Vec2(0,0));
vidSprite->setScale(1.26, 1.26);
vidSprite->setName("videoLayer");

//普通にスプライトを配置するように使える
this->addChild(vidSprite);
//mp4のファイルで初期化
vidSprite->initWithFile("ファイル名.mp4");
//runで再正開始
vidSprite->run(true);
```

と行った感じで使用できます。
また、少しコードを変更すれば、透過動画も使えるようになるので利用の幅は結構広そうです。