月餅のブロマガ

AviutlのスクリプトでDLLを使う(備忘録)

2020/07/17 21:23 投稿

  • タグ:
  • Aviutl

AviutlのスクリプトでDLLを使う(備忘録)

目次

前置き

先日いい感じに明度・コントラストを調整するスクリプトを作りまして、 それを作る際にピクセル単位の処理をする必要があったのですが、 処理速度の関係上CでDLLを作ったほうがいいなとなって色々と試行錯誤したのでメモを残しておきます。
特に今回はアニメーション効果でピクセル単位の処理をするという前提で記述していきますが、 カスタムオブジェクト等でobj.loadしたものをobj.drawしたものに対して 利用するなども同じ方法だと思われます。
また、DLLの作成にはgcc(MinGW)を利用します。

そもそもAviutlでピクセル単位の処理をする方法って2つありまして、 1つがobj.getpixel()obj.putpixel()を利用する方法なんですが、 この方法はLuaで完結する代わりにめちゃくちゃに重いんですよね。 再生位置をずらすだけで数秒固まる状態で、プレビュー再生なんてできたものじゃありません。 小さい画像だったら役に立つのかもしれないですが...エンコード限定にしても相当遅くなるでしょうね。
そしてもう1つがobj.getpixeldataputpixeldataを利用する方法で、 requireでロードしておいたモジュールにobj.getpixeldataの戻り値を渡してやることで DLLに処理させてやる方法です。

もちろん簡単なスクリプトなら何か回避策を考えて、わざわざDLLを作らずにフレームバッファとかを利用してやるのがいいんですが、 そうもいかないときはDLLを利用することになるでしょう。

準備

開発環境などは省略します。

  • CのLuaライブラリ
    LuabinariesからDL可能。lua5_1_4_Win32_dll8_libをDLする。
    lua51.dllとincludeディレクトリ内の各種ヘッダーファイルが必要。

サンプルコード(Lua)

ソース

--track0:a,-100,100,0,1
--track1:b,-100,100,0,1
--track2:c,0,500,100,1
--track3:d,0,5,1,0.01

require("moduleName")

local data, w, h = obj.getpixeldata()
moduleName.method(data, w, h, obj.track0, obj.track1, obj.track2, obj.track3)
obj.putpixeldata(data)

説明

最初のコメントはスライダーの取得(Aviutl側の拡張)。
requireでモジュールを読み込み(DLLファイル名: moduleName.dll)。 このときmoduleNameがglobal空間に追加される。
methodの定義などはDLL側。

サンプルコード(C)

ソース

#include <stdint.h>
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
int method1(lua_State *L){
    uint32_t *data = (uint32_t*) lua_touserdata(L, 1);
    uint32_t w = (uint32_t) lua_tointeger(L, 2);
    uint32_t h = (uint32_t) lua_tointeger(L, 3);
    float a = (float) lua_tonumber(L, 4);
    float b = (float) lua_tonumber(L, 5);
    float c = (float) lua_tonumber(L, 6);
    float d = (float) lua_tonumber(L, 7);


    for(uint32_t y = 0; y < h; y++){
      for(uint32_t x = 0; x < w; x++){
        uint64_t i = x + w * y;
        float r = (data[i] >> 16) & 0xFF;
        float g = (data[i] >>  8) & 0xFF;
        float b =  data[i]        & 0xFF;
        float a = (data[i] >> 24) & 0xFF;

        /* RGBAに対して処理 */

        if(r < 0x00) r = 0x00;
        if(g < 0x00) g = 0x00;
        if(b < 0x00) b = 0x00;
        if(a < 0x00) a = 0x00;
        if(r > 0xFF) r = 0xFF;
        if(g > 0xFF) g = 0xFF;
        if(b > 0xFF) b = 0xFF;
        if(a > 0xFF) a = 0xFF;

        data[i] = (((uint8_t)r)  16) | (((uint8_t)g)  8) | ((uint8_t)b) | (((uint8_t)a)  24);
      }
    }

    lua_settop(L, 1);
    return 1;
}
static luaL_Reg reg[] = {
  {"method", method1},
  {NULL, NULL}
};

__declspec(dllexport) int luaopen_moduleName(lua_State *L){
  luaL_register(L, "moduleName", reg);
  return 1;
}

解説


lua.hlauxlib.hlualib.hをincludeする。
いくつかのサイトではlua.hppをincludeするとしていたがこれはC++を利用する場合。中身としては前述の3つのヘッダファイルをincludeしている。

次に具体的な処理をする関数が記述してあるが、それを飛ばして最後の9行を先に見て欲しい。

luaL_Reg型のreg配列にはLuaから参照可能なメソッドを配列に格納している。 解放されないようにstaticをつけている(と思われる)。
各要素の第1要素がLuaから参照する際の固有名、第2要素が関数ポインタ。
番兵として{NULL, NULL}を入れておく。

luaopen_moduleNameはおそらくモジュールの(初回)読み込み時に呼び出される関数で、 lual_registerでLuaから関数(メソッド)を呼び出せるよう登録している。
__declspecはDLLから参照できるようにするための記述(だったはず)。externみたいな。
return 1;の理由は覚えていない。

最後に先程のluaL_Reg型配列に登録した関数について(サンプル内でいうmethod1)。
引数としてlua_State*型変数をとる。

スタックとして扱うハンドルみたいなもので、これを介して引数や戻り値のやり取りを行う。 スタックのn番目にはLuaで呼び出したときのn番目の引数が格納されている。

イメージデータは4byteの値なのでstdint.hのuint32_t*型変数に格納している。スクリプトWikiだとwindows.hのDWORD型に格納してた気がする。
lua_touserdataはvoid*型を返してくるのでuint32_t*にキャストして利用する。 一応uint8_t型のb, g, r, aをメンバに持つ構造体ポインタを指定してもできるらしい。
同様の関数としてlua_tointeger、lua_tostringなどがある。詳細はlua.hを参照。

forループがネストされてる部分では座標(x, y)のピクセルに対して処理を行っている。 なお画像データ自体は1次元配列なのでインデックスを計算している。

画像データの各値はARGBの順番に並んでいるのでビットマスクとシフトで取り出す。スクリプトWikiの受け売り。
それに対して処理をした後画像データに上書きする。 このとき引数としてとったlua_State*で参照されるスタックに直接上書きしている。

最後にlua_settopでスタックトップを指定しているが必須かどうかはわからない。
returnでは引数の数を返す。スタックに余聞な値が残っていても構わない。
サンプルコードではdataのみを返せばいいので1を指定している。

コンパイルについて


includeディレクトリをインクルードフォルダに指定し、lua51.dllをリンクしてコンパイルする。 以下のディレクトリ構成の場合、

/lua
/includes
lauxlib.h
lua.h
...
lauxlib.h
lua.h
...

gcc -o a.dll toneCorrection.c -I lua/include -L lua -l lua51 -shared

-Iオプションでインクルードディレクトリの指定(今回はlua/include)
-Lオプションでリンクファイルのディレクトリの指定(今回はlua)
-lでリンク(今回はlua51.dll)
-sharedでDLL生成を指定(これがないとexeを作ろうとしてエントリーポイントがないって怒られる)
ちなみにAviutlは一度DLLを読み込むとロックしてしまうので再コンパイルして上書きするときはAviutlを再起動する必要があります。

デバッグについて


C側からLuaの関数を呼び出します。

lua_getglobal(L, "debug_print");
lua_pushnumber(L, something_to_print);  //必要に応じて lua_pushstring などを利用
lua_call(L, 1, 0);

詳しい説明は省きますが1行目でdebug_printを指定、
lua_pushnumber(lua_tonumberなどと同様色んな型がある)でスタックにdebug_printに渡す引数をpush、
lua_callで引数を1つ、戻り値を0個として呼び出しています。
詳細な呼び出しにはlua_pcallを使えたと思います。
あとはluaでスクリプトを組むときと同じソフトで監視できます。 私はDEMON使ってるのですがもっと良いソフトあったら教えて下さい。

その他

修正、質問などあったらコメント(気づかなかったらごめんなさい)もしくはTwitter(@Tsukina_7mochi)まで。

コメント

コメントはまだありません
コメントを書き込むにはログインしてください。

いまブロマガで人気の記事