トップ | puarts.com
ロゴ
「UI開発」に関連する記事一覧
0  

オリジナル映像編集ソフトにレイヤーモードを切り替えられる機能をつけました。

下記の15種類のレイヤーモードをとりあえず実装しました。

説明とか何も入ってないですが、どなたかのお役に立つかもしれないので、レイヤーモードごとの画像ブレンドをする関数のC++ソースコードを載せておきます。

ただし、Photoshopなどのレイヤーモードと比べると結果が異なる可能性は十分にありますのでご注意ください。



typedef float BlendCalcType;

inline BlendCalcType BlendValue(
    BlendCalcType base_value, BlendCalcType blend_value
)
{
    return blend_value;
}

inline BlendCalcType DarkenValue(
    BlendCalcType base_value, BlendCalcType blend_value
)
{
    return base_value < blend_value? base_value : blend_value;
}

inline BlendCalcType MultiplyValue(
    BlendCalcType base_value, BlendCalcType blend_value
)
{
    BlendCalcType tmp_value = blend_value/255.0f;
    if( tmp_value > 1.0f)
    {
        tmp_value = 1.0f;
    }
    return base_value * tmp_value;
}

inline BlendCalcType BurnValue(
    BlendCalcType base_value, BlendCalcType blend_value
)
{
    BlendCalcType tmp_value;
    if (blend_value == 0) tmp_value = 0;
    else tmp_value = 255 - ((255 - base_value) * 255 / blend_value);

    return (tmp_value < 0)? 0 : tmp_value;
}

inline BlendCalcType LightenValue(
    BlendCalcType base_value, BlendCalcType blend_value
)
{
    return base_value > blend_value? base_value : blend_value;
}

inline BlendCalcType AddValue(
    BlendCalcType base_value, BlendCalcType blend_value
)
{
    BlendCalcType tmp_value = base_value + blend_value;
    if( tmp_value > 255.0 )
    {
        tmp_value = 255.0;
    }
    return tmp_value;
}

inline BlendCalcType ScreenValue(
    BlendCalcType base_value, BlendCalcType blend_value
)
{
    return 255 - ((255 - base_value) * (255 - blend_value)) / 255;
}

inline BlendCalcType DodgeValue(
    BlendCalcType base_value, BlendCalcType blend_value
)
{
    BlendCalcType tmp_value;
    if (blend_value == 255){ tmp_value = 255; }
    else{ tmp_value = base_value * 255 / (255 - blend_value); }

    return (tmp_value > 255)? 255 : tmp_value;
}

inline BlendCalcType OverlayValue(
    BlendCalcType base_value, BlendCalcType blend_value
)
{
    BlendCalcType tmp_value;
    if (base_value < 128){ tmp_value = base_value * blend_value * 2 / 255; }
    else{ tmp_value = 2 * (base_value + blend_value - base_value * blend_value / 255) - 255; }

    return (tmp_value > 255)? 255 : tmp_value;
}

inline BlendCalcType SoftLightValue(
    BlendCalcType base_value, BlendCalcType blend_value
)
{
    return blend_value < 128?
        (pow((base_value / 255), ((255 - blend_value)) / 128)) * 255
        : pow((base_value / 255), (128 / blend_value)) * 255;
}

inline BlendCalcType HardLightValue(
    BlendCalcType base_value, BlendCalcType blend_value
)
{
    BlendCalcType tmp_value;
    if (blend_value < 128){ tmp_value = base_value * blend_value * 2 / 255; }
    else{ tmp_value = 2 * (base_value + blend_value - base_value * blend_value / 255) - 255; }

    return (tmp_value > 255)? 255: tmp_value;
}

inline BlendCalcType SubtractValue(
    BlendCalcType base_value, BlendCalcType blend_value
)
{
    BlendCalcType tmp_value = base_value - blend_value;
    return tmp_value < 0? 0 : tmp_value;
}

inline BlendCalcType DifferenceValue(
    BlendCalcType base_value, BlendCalcType blend_value
)
{
    return fabs(base_value - blend_value);
}

inline BlendCalcType DivideValue(
    BlendCalcType base_value, BlendCalcType blend_value
)
{
    if( blend_value==0 ){ return 255; }
    BlendCalcType tmp_value = base_value * 255 / blend_value;
    return tmp_value>255? 255: tmp_value;
}

inline BlendCalcType ExclusionValue(
    BlendCalcType base_value, BlendCalcType blend_value
)
{
    return base_value + blend_value - 2 * base_value * blend_value / 255;
}

template<typename _Type>
void BlendMain(
    _Type       *io_base_img, 
    const _Type *in_overlay_img,
    const u8    *in_alpha_data, 
    u32 width_step, u32 height, u32 num_channels, 
    BlendCalcType (*BlendFunc) ( BlendCalcType, BlendCalcType )
)
{
    float transmittance;
    const _Type *blend_ptr = in_overlay_img;
    const u8 *alpha_ptr  = in_alpha_data;
    _Type *dst_ptr     = io_base_img;
    _Type *dst_end_ptr = dst_ptr + width_step * height;
    _Type *c_ep;
    double tmp_value;
    for( ; dst_ptr!=dst_end_ptr; ++alpha_ptr ) 
    {
        transmittance = (*alpha_ptr)/255.0f;
        for( c_ep = dst_ptr + num_channels; dst_ptr!=c_ep; ++dst_ptr, ++blend_ptr) 
        {
            *dst_ptr = *dst_ptr * (1.0f-transmittance) +
                (_Type)BlendFunc( (BlendCalcType)*dst_ptr, (BlendCalcType)*blend_ptr ) * transmittance;
        }
    }
}

オリジナル合成ソフト開発でレイヤー選択機能を実装しました。この機能実装で面倒なことはレイヤーがトランスフォームされているということです。どうするのが一番簡単かなと考えた結果、クリックした座標における一番上のトランスフォームされたレイヤーを探すために、ある座標があるレイヤーの上にあるかどうかを逆アフィン変換を使って判定していくのが楽かなという結論に至りました。実装したC++関数の大まかなソースコードを載せておきます。


struct AffineParam{
    float anchor_pt_x, anchor_pt_y;
    float translate_x, translate_y;
    float scale_x, scale_y;
    float rotate_angle;
};

inline bool IsOnLayer( int x, int y, int width, int height, const AffineParam ¶m )
{
    float centered_x, centered_y;
    int src_x, src_y;

    float rotate_rad = param.rotate_angle * PI / 180.0f;
    float sx_mult    = 1.0f / param.scale_x;
    float sy_mult    = 1.0f / param.scale_y;
    float tx         = param.translate_x;
    float ty         = param.translate_y;
    float ax         = param.anchor_pt_x + tx;
    float ay         = param.anchor_pt_y + ty;

    centered_x = x - ax;
    centered_y = y - ay;

    src_x = (int)( (centered_x * cos(rotate_rad) - centered_y * sin(rotate_rad)) * sx_mult - tx + ax );
    src_y = (int)( (centered_x * sin(rotate_rad) + centered_y * cos(rotate_rad)) * sy_mult - ty + ay );

    return 0<=src_x&& src_x<width&& 0<=src_y&& src_y<height;
}

複数レイヤーを上から順番に見てゆき、この関数でそれぞれのレイヤー上にカーソルがのっているかどうかを判定し、最初にtrueとなったレイヤーを選択されるレイヤーとして選ぶようにします。

現在開発中のオリジナル合成ソフトで基本的なトランスフォームをレイヤーごとに操作できるようにするためにアフィン変換を行うtemplate関数を作りました。アフィン変換は簡単な式で実装できるんですが、昔大学の授業で実装して以来だったので正直あまり覚えていなかったです。社会人になって数学に触れる機会がめっきり減ったので危機感を覚えますね。

とにかくアフィン変換部分は無事動き、合成ソフトではレイヤーごとにトランスフォームを編集できるようになりました。

ざっくりですが作った関数はこんな感じです。


struct AffineParam{
    float anchor_x, anchor_y;
    float translate_x, translate_y;
    float scale_x, scale_y;
    float rotate_angle;
};

template<typename _Type>
wsp::Result wsp::img::AffineTransform( 
    const _Type *in_src, 
    _Type *o_dest,
    int width, int height, int num_channels, 
    const struct wsp::img::AffineParam &param
)
{
    if( width<1 || height<1 || num_channels<1 )
    {
        return wsp::ResultInvalidArgument();
    }

    int wstep = width*num_channels;
    memset( o_dest, 0, sizeof(_Type)*wstep*height );

    if( param.scale_x==0.0f || param.scale_y==0.0f )
    {
        return wsp::ResultSuccess();
    }

    int x, y;
    float centered_x, centered_y;
    int trans_x, trans_y;

    float rotate_rad = param.rotate_angle * PI / 180.0f;
    float sx_mult = 1.0f / param.scale_x;
    float sy_mult = 1.0f / param.scale_y;
    float tx = param.translate_x;
    float ty = param.translate_y;
    float ax = param.anchor_x;
    float ay = param.anchor_y;

    _Type *dst_ptr = o_dest;
    const _Type *src_ptr, *end_ptr;
    for( y=0; y<height; ++y )
    {
        for( x=0; x<width; ++x )
        {
            centered_x = x - ax;
            centered_y = y - ay;

            trans_x = (int)( (centered_x * cos(rotate_rad) - centered_y * sin(rotate_rad)) * sx_mult - tx + ax );
            trans_y = (int)( (centered_x * sin(rotate_rad) + centered_y * cos(rotate_rad)) * sy_mult - ty + ay );

            if( trans_y<0 || trans_y>=height || trans_x<0 || trans_x>=width )
            {
                dst_ptr += num_channels;
                continue;
            }
            src_ptr = in_src + trans_y * wstep + trans_x * num_channels;
            end_ptr = src_ptr + num_channels;
            for( ; src_ptr!=end_ptr; ++src_ptr, ++dst_ptr )
            {
                *dst_ptr = *src_ptr;
            }
        }
    }
    return wsp::ResultSuccess();
}

次はキーフレームを各パラメータに打てるようにすることなんですが、こちらは中身よりGUIの方が実装が大変そうです。まぁ気長にやっていこうと思います。

とても簡易的ではありますが、オリジナル合成ソフトにレンダーキューを加えました。設定できるパラメータは出力先パスだけという簡素なものですが、合成データをイメージシーケンスとしてなら書き出しができる状態になりました。

ついでにGUIスキンも黒っぽくしてみました。完全にAEのデザインのパクリです。Qtだとstyle sheetでGUIのデザインを変更できるのでとても楽ちんです。下記ページにstyle sheetのサンプルがたくさん載っていて参考になります。

http://doc.qt.digia.com/qt/stylesheet-examples.html

とりあえず合成データの読み込みから書き出しまで最低限必要な操作の実装は終わりました。と言っても実はまだレイヤーごとの座標やスケール値にキーを打つ部分の実装をしていないので、次はその辺りを実装していこうと思います。

エフェクトビューで値を編集できるようにしました。QTreeWidgetにsetIndexWidgetでパラメータ設定用のスライダーやチェックボックスをセットして作りました。

次はレンダーキューみたいのを作ろうかと思います。

オリジナル合成ソフト開発記録です。エフェクトビューを追加しました。エフェクトをレイヤーごとに適用できるようにしましたが、まだエフェクトのパラメータは変更できない状態です。とりあえず、後はエフェクトのパラメータを変更できるようにして、基本的なキーを打てるようにし、レンダーキューみたいなものを作れば合成ソフトっぽくなりそうです。まだまだ道のりは長い。

オリジナル合成ソフト開発記録です。ひどいインターフェースですが、ようやくレイヤーの更新周りの操作ができるようになりました。まだまだまともに使えるソフトになるには程遠そうです。

かなり前から作っては放置を繰り返していたオリジナル合成ソフトですが、少し進んだので開発経過を記録しておきます。一緒に掲載した動画のようにレイヤー部分の処理を今実装中です。とりあえずレイヤーを読み込んで、それらをキーフレームアニメーションさせられるところまで作りました。

After Effectsのような合成ソフトに近づくにはまだまだ道のりは長そうです。

0  

0.026 sec
にほんブログ村 ゲームブログ ファイアーエムブレムへ にほんブログ村 デザインブログ コンピュータグラフィックスへ

Copyright(C)2006-2018 wsp All Rights Reserved