After Effects エフェクトプラグインをゼロから作って見よう 4回目 "新しいSkelton"
仕事でちょっと悩んでて気晴らしにスケルトンの修正したら意外と簡単にできたので、今回はそれの解説です。
修正したスケルトン
すみません、修正というか以前作ったTiny版F's pluginsのライブラリーからほとんどコピペしました。
変更点・追加点
”NF_Plugins”の直下に"_NFLib"フォルダを追加して新しいライブラリファイルを追加しました。
AEInfo.h エントリー関数からセレクタ毎に処理をカプセル化したクラスAEInfoクラスが定義してある
NFWorld.h PF_EffectWorldを使いやすくラッパーしたクラスNFWorldを定義
NFUtils.h F's pluginで使ってたマクロとかをまとめたもの
NFLibVersion.h このライブラリのバージョンを定義
AE_SDK.h ファイル毎に書いていたSDKのインクルードファイルをまとめた物
とりあえず、gitからクローンかプルしてビルドしてAfterEffectsに登録すると以下の感じになる。スケルトンとして使いそうなパラメータのサンプルが全部入ってます。8/16/32bitにも対応してます。マルチフレームレンダーにも対応(何もしてないので)してるのでエラーアイコンが出ていません。
Aboutダイアログ
プラグインの情報の表示で標準のダイアログではなく、スクリプトでダイアログを作成して表示してます。旨く作ればかっこいいダイアログが出来ます(僕はまぁしたことないですが)
プラグイン内でスクリプトを実行するサンプルでもあります。
パラメータを獲得する簡単なラッパーを装備
ERR(GetFLOAT(ID_SHIFTX, &infoP->shiftX));
GetParams関数で上記のようにわかりやすく記述。Render/SmartPreRenderに分けて書く必要がありません。
PF_EFFectWorldを簡単に使えるNFWorldクラス
NFWorld* src = new NFWorld(input, in_data, pixelFormat());
上記のように作成するだけで、描画に必要な処理を自動的に終わらせてくれる。簡単な描画処理はすでに実装してます。
新規プラグイン作成手順
このスケルトンのフォルダを複製して新しい名前にリネームしてソリューションに追加。
NF_Target.hの修正
元Skelto,hの修正 インデックス番号/ParamInfoの修正
元Skelton.cppの::ParamsSetup関数でパラメータを構築
元Skelton.cppの::GetParams関数でパラメータをparamInfoに取り込むコードを記述
元Skelton.cppの::Exec関数でエフェクトの実装を行う。ここでやればSmarkFXにも簡単に対応。
って感じに最低限のコーディングでエフェクトが作成できます。
実際に作って見る
このスケルトンを使って前回のShiftPerを作って見ましょう。
前回作ったShiftPerを削除
ソリューションから削除して、フォルダーをエクスプローラーで削除します。(Gitには残しておきたいのでリネームして残してます)
スケルトンフォルダを複製してリネーム、ソリューションに追加
前回と同じ感じです。
NF_Target.hの修正
NF_DESCRIPTIONを書き換えただけ
ShiftPer.hの修正
//UIのパラメータ
typedef struct ParamInfo {
NFWorld* inWld;
NFWorld* outWld;
PF_FpLong shiftX;
PF_FpLong shiftY;
A_long shiftXPx;
A_long shiftYPx;
} ParamInfo, * ParamInfoP, ** ParamInfoH;
//ユーザーインターフェースのID
//ParamsSetup関数とRender関数のparamsパラメータのIDになる
enum {
ID_INPUT = 0, // default input layer
ID_SHIFTX,
ID_SHIFTY,
ID_NUM_PARAMS
};
まぁ本来はここでパラメータとかの仕様を決めます。
ShiftPer.cppの修正 ParamSetup
// **********************************************************
PF_Err ShiftPer::ParamsSetup(
PF_InData* in_dataP,
PF_OutData* out_dataP,
PF_ParamDef* paramsP[],
PF_LayerDef* outputP)
{
PF_Err err = PF_Err_NONE;
Init();
m_cmd = PF_Cmd_PARAMS_SETUP;
in_data = in_dataP;
out_data = out_dataP;
PF_ParamDef def;
//----------------------------------------------------------------
AEFX_CLR_STRUCT(def);
PF_ADD_FLOAT_SLIDER(
"shiftX(%)", //Name
-30000, //VALID_MIN
30000, //VALID_MAX
-200, //SLIDER_MIN
200, //SLIDER_MAX
1, //CURVE_TOLERANCE
0, //DFLT
1, //PREC
0, //DISP
0, //WANT_PHASE
ID_SHIFTX
);
//----------------------------------------------------------------
AEFX_CLR_STRUCT(def);
PF_ADD_FLOAT_SLIDER(
"shiftY(%)", //Name
-30000, //VALID_MIN
30000, //VALID_MAX
-200, //SLIDER_MIN
200, //SLIDER_MAX
1, //CURVE_TOLERANCE
0, //DFLT
1, //PREC
0, //DISP
0, //WANT_PHASE
ID_SHIFTY
);
//----------------------------------------------------------------
out_data->num_params = ID_NUM_PARAMS;
return err;
};
パラメータは二つだけ
ShiftPer.cppの修正 GetParams
PF_Err ShiftPer::GetParams(ParamInfo* infoP)
{
PF_Err err = PF_Err_NONE;
ERR(GetFLOAT(ID_SHIFTX, &infoP->shiftX));
if (!err){infoP->shiftX /= 100;}
ERR(GetFLOAT(ID_SHIFTY, &infoP->shiftY));
if (!err) { infoP->shiftY /= 100; }
return err;
};
ShiftPer.cppの修正 Exec
PF_Err ShiftPer::Exec(ParamInfo* infoP)
{
PF_Err err = PF_Err_NONE;
NFWorld* src = new NFWorld(input, in_data, pixelFormat());
NFWorld* dst = new NFWorld(output, in_data, pixelFormat());
dst->Copy(src);
if ((infoP->shiftX != 0)|| (infoP->shiftY != 0)) {
//init_xorShift(frame()); // 乱数の初期化
//実際に動かすピクセル数を計算
infoP->shiftXPx = (A_long)(infoP->shiftX * (PF_FpLong)(src->width()) + 0.5);
infoP->shiftYPx = (A_long)(infoP->shiftY * (PF_FpLong)(src->height()) + 0.5);
//画像を登録
infoP->inWld = src;
infoP->outWld = dst;
switch (pixelFormat())
{
case PF_PixelFormat_ARGB128:
iterate32(src->world, (void*)infoP, Shift32, dst->world);
break;
case PF_PixelFormat_ARGB64:
iterate16(src->world, (void*)infoP, Shift16, dst->world);
break;
case PF_PixelFormat_ARGB32:
iterate8(src->world, (void*)infoP, Shift8, dst->world);
break;
default:
break;
}
}
delete src;
delete dst;
return err;
};
PreRenderとか考えなくてここに描画のメインを書けばOK
実際の描画のコードを記述
static PF_Err
Shift8(
void* refcon,
A_long xL,
A_long yL,
PF_Pixel8* inP,
PF_Pixel8* outP)
{
PF_Err err = PF_Err_NONE;
ParamInfo* infoP = reinterpret_cast<ParamInfo*>(refcon);
A_long w = infoP->inWld->width();
A_long h = infoP->inWld->height();
A_long nx = (xL - infoP->shiftXPx) % w;
if (nx < 0) nx += w;
A_long ny = (yL - infoP->shiftYPx) % h;
if (ny < 0) ny += h;
*outP = infoP->inWld->GetPix8(nx, ny);
return err;
}
8bit部分のコード。NFWorldのGetPix8関数でピクセルを獲得してます。
16/32はほぼ同じ。
完成
ここまでの作業5分もかかりませんでした。まぁコードがほぼ完成していたので当たり前ですが。プロジェクトパネル見ると32bitモードで動いているのでしっかりSmartFXにも対応していることも分かります。
オマケでデバッグの仕方
これだけだとつまらないのでDebuggerの使い型を説明します。
こんな簡単な奴だとほぼ必要ないのですが、少し複雑になるとバグが出まくります。どこがバグってるか調べるにはデバッガーを使うのが楽です。
スタートアッププロジェクトに設定
ソリューションパネルでプロジェクトを選択して右クリック「スタートアッププロジェクトの設定」を選んで設定します。
Dubugモードに
左上のパネルにあるポップアップからDebugを選ぶ。作成中はほぼDebugモードですね。横の構成もx64を選んでおいてください。
ホストアプリを設定
プロパティから「デバッグ」タブ、「コマンド」の項目をターゲットとするAfterFX.exeを選ぶ。昔はいろいろ設定しないと駄目だったけど、最近はこれだけ
デバッグ開始
左上のパネルでローカルWindowsデバッガーをクリックすれば実行です。
AfterEffectsが普通に起動しますので、適当な画像ファイルをインポートしてコンポに入れてエフェクトを適応させてデバッグです。
起動時に問題があったらそこで止まります。
コードの怪しそうな場所にブレークポイント(F09)設定すればそこに来れば止まります。
またエラーが出たら大抵はそのコードの前がバグってるので、「呼び出し履歴」で戻り確認します。
主立った変数の値を確認できます。
最後に
ゼロから作るってタイトルに嘘ついて、F's pluginsのライブラリを移植してしまいました。久しぶりにゼロから作ったら途方もなく時間かかって嫌になってついやってしまいました。
次はAfterEffectsに関するプログラミング的な事を記事にしようと思ってます。変数とかです。そういえばコールバック関数の説明もしていないので。