LoginSignup
9
9

More than 5 years have passed since last update.

cocos2d-xでモザイク処理

Last updated at Posted at 2015-07-27

環境

Mac OS 10.10.3
cocos2d-x 3.7

なんでモザイク?

最近、cocos2d-xに触ってないから。

なんとなく。

要件

  1. モザイクのタイルはDrawNodeで描画する
  2. モザイクの粒度を指定できる
  3. モザイクの元はNodeで指定できる
  4. 指定していないNode(背景など)はモザイクにしない

ソース

githubにあげているものにコメントを付けただけのものです。

FilterMozaic.h
#include "cocos2d.h"

class FilterMozaic : public cocos2d::DrawNode // 1. DrawNodeで描画する
{
public:
    // 2. モザイクの粒度を指定できる
    // 3. モザイクの元はNodeで指定できる
    static FilterMozaic* createWithTarget(cocos2d::Node* target, cocos2d::Size mozaicSize);
    bool initWithTarget(cocos2d::Node* target, cocos2d::Size mozaicSize);
    bool refreshWithTarget(cocos2d::Node* target, cocos2d::Size mozaicSize);

protected:
    cocos2d::Size _mozaicSize;
};
FilterMozaic.cpp
#include "FilterMozaic.h"

using namespace cocos2d;

/**
 * 普通のcreateメソッド、読む必要なし
 */
FilterMozaic* FilterMozaic::createWithTarget(Node* target, Size mozaicSize)
{
    FilterMozaic* instance = new FilterMozaic();
    if (instance != nullptr && instance->initWithTarget(target, mozaicSize)) {
        instance->autorelease();
        return instance;
    }

    delete instance;
    instance = nullptr;
    return instance;
}

/**
 * 実質、refreshWithTargetのシノニム
 */
bool FilterMozaic::initWithTarget(Node* target, Size mozaicSize)
{
    if (!DrawNode::init()) return false;

    return this->refreshWithTarget(target, mozaicSize);
}

/**
 * メインの処理
 * Actionとしても取り回しが利くように、作成済みFilterMozaicを更新できるようにする
 */
bool FilterMozaic::refreshWithTarget(Node* target, Size mozaicSize)
{
    _mozaicSize = mozaicSize;

    // glReadPixelで読むときに使う
    float contentScaleFactor = Director::getInstance()->getContentScaleFactor();

    float minPix = 1.0f / contentScaleFactor;

    // 小さすぎる値で無限ループするのを回避
    if (_mozaicSize.width < minPix)
        _mozaicSize.width = minPix;
    if (_mozaicSize.height < minPix)
        _mozaicSize.height = minPix;

    // 描画済み内容のclear
    this->clear();

    // 元Nodeがなければ何もしない
    if (target == nullptr) return false;

    bool originalVisibility = target->isVisible();
    Vec2 originalScale      = Vec2(target->getScaleX(), target->getScaleY());

    this->setContentSize(target->getContentSize());

    Director* director = Director::getInstance();

    Size contentSize = target->getContentSize();
    Vec2 anchorPoint = target->getAnchorPoint();

    // 0座標を左下に固定
    Vec2 leftBottom  = target->getPosition() - Size(contentSize.width * anchorPoint.x, contentSize.height * anchorPoint.y);

    // オフスクリーンレンダリング用にメンバを一時的に変更する
    target->setVisible(true);
    target->setScale(1.0f);

    // 4. 指定していないNode(背景など)はモザイクにしない
    // 元Nodeだけをオフスクリーンレンダリングする
    RenderTexture* rt = RenderTexture::create(director->getVisibleSize().width, director->getVisibleSize().height);
    rt->begin();
    target->draw(director->getRenderer(), target->getNodeToParentTransform(), false);
    director->getRenderer()->render();

    // モザイクのタイルの間隔でピクセルを読み、その色で矩形を描画する
    for (float x = 0.0f; x <= contentSize.width - _mozaicSize.width; x += _mozaicSize.width) {
        for (float y = 0.0f; y <= contentSize.height - _mozaicSize.height; y += _mozaicSize.height) {
            // 元Nodeだけしか描画されていないので、背景などの余計な情報は含まれない
            void* buffer = malloc(sizeof(Color4B)*1);
            glReadPixels((GLint)(int)((leftBottom.x + x) * contentScaleFactor),
                         (GLint)(int)((leftBottom.y + contentSize.height - y) * contentScaleFactor),
                         1, 1, GL_RGBA, GL_UNSIGNED_BYTE, buffer);

            Color4B* color = static_cast<Color4B*>(buffer);
            // 完全に透過なピクセルは無視
            if (color->a <= 0.0f) continue;

            Vec2 verts[4] = {
                Vec2(x,                     contentSize.height - y),
                Vec2(x + _mozaicSize.width, contentSize.height - y),
                Vec2(x + _mozaicSize.width, contentSize.height - y - _mozaicSize.height),
                Vec2(x,                     contentSize.height - y - _mozaicSize.height)
            };

            this->drawPolygon(verts, 4, Color4F(color->r / 255.0f, color->g / 255.0f, color->b / 255.0f, color->a / 255.0f), 0, Color4F(0.0f, 0.0f, 0.0f, 0.0f));

            free(buffer);
        }
    }

    // オフスクリーンレンダリング終了
    rt->end();

    // オフスクリーンレンダリング用に変更したメンバを元に戻す
    target->setVisible(originalVisibility);
    target->setScale(originalScale.x, originalScale.y);

    // 自身のscaleも元Nodeに合わせても良い (好みや使い方の問題)
    // this->setScale(originalScale.x, originalScale.y);

    return true;
}

サンプル

BEFORE

BEFORE

AFTER

AFTER

9
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
9