この記事を読む前に
AEのプラグインを作成するにあたって、記事が古かったりそもそも日本の記事があまりにも少ない印象だったので書いてみました。
初めてこういった記事を書くので慣れていない部分が多く、とても読みづらくなってしまうかもしれませんがご容赦頂ければ幸いです、、!
また間違った知識の元書いてしまっている部があった場合はご指摘いただけると大変助かります!
はじめに
AEのエフェクトプラグインを作るにあたって、以下のような知識が前提となります
- C++である程度コーディングができる
- 画像処理の基礎的なアルゴリズム
- 並列処理プログラムが書ける
と言ってもハードなプラグインを作ろうとしない限り
C++でコーディングができることと画像処理の根本的な仕組みが分かっていればある程度の処理プログラムは書けます
( 実際自分も超専門的なアルゴリズムを理解しているわけではないです笑 )
まあそんなに頭固くして物事考えても仕方ないので取り合えず触れてみる精神が大切だと思いますので興味があれば十分です!
今回はいきなり難しいことをやってもよくわからなくなるだけなので、
簡単に色素の順番を入れ替えるだけの処理書いていきます。
環境
今回は以下の環境で開発していきます。
Adobe社のソフトウェア 拡張機能・プラグイン のSDKはこちらのサイトからダウンロードできます。
- Windows 10
- Adobe After Effects 2022
- Microsoft Visual Studio 2019
- October 2021 After Effects SDK Windows
プラグインテンプレートのあれこれ
概要
After Effects のプラグインSDKにはいくつかのテンプレートが用意されています。
例えば After Effects その物を拡張する専用のものなどがありますが、
ここではそこまで触れないので興味がある方はこちらの記事で詳しく紹介されているのでご覧ください。
解凍
After Effects のプラグイン専用SDKをダウンロードしてきたら
今後プラグインの開発をしていく為のディレクトリを用意して解凍しておきます。
例:C:\AESdk\ など
※注意
解凍するときの注意点として
AESDK は配布されているテンプレートそのものを改造してコーディングしていくので、
将来的に移動したくなる場合などを考慮して解凍することをお勧めします。
テンプレートの選び方
今回触る部分はいわゆる エフェクトプラグイン の開発なので、
主に「Examples」フォルダの中の「Effect」フォルダ内のテンプレートと「Template」フォルダ内のテンプレートを選ぶと良いです。
今回は基礎的な内容が簡単に書かれている Template フォルダ内の Skeleton テンプレートを改造していくことにします。
Skeletonテンプレートの改造
テンプレートのコピー
今後開発するにあたって、いちいちテンプレートをダウンロードしてくるのはめんどくさいので
最初にコピーしてから改造することをお勧めします。
コピーする際はプロジェクトのフォルダごとコピーしてしまいましょう!
今回はコピーしてからフォルダ名を color_manager とします。
プロジェクトのオープン
コピーできたらフォルダを開き Win フォルダの中にある Skeleton.sln の名前も変更してしまいます。
先ほどと同様に color_manager.sln といった具合に変更します。
無事変更できたらそのままソリューションファイルを開きます。
開いた際に VisualStudio から
「Skeleton に対するセキュリティ警告」
といったポップアップが出現する場合がありますが、特に気にせずOKで大丈夫です。
開いたら画像のようなポップアップが出現するので
お使いの Windows SDK のバージョンなどに合わせてOKをクリックします。
( 通常はデフォルトのままで問題ないです )
無事再ターゲットが完了すればソリューションが問題なく開かれます。
※ここで失敗するようであれば先ほどの Windows SDK などの設定が悪い可能性があるのでやり直してみてください。
プロジェクトの名前の変更
まずビルドした際の拡張機能の名前を変更していきます。
( なんだかんだ言ってここが一番めんどくさかったりします笑 )
まずソリューション内に Skeleton という名前の C++ プロジェクトがあるのでこいつの名前を変更してやります。
普通に右クリックから「名前の変更」をしてやれば問題なく名前が変更されます。
ただこれだけでは After Effects に読み込ませたときにプラグイン名が変わっていないで、
次にそっちの設定もしてやる必要があります。
Skeleton.cpp
C++ プロジェクト内の Skeleton.cpp を開きます。
下の方に PluginDataEntryFunction という名前の関数あります。
こいつは簡単に言ってしまえば After Effects がこのプラグインを読み込んだ時に、どんな内容のプラグインなのかを確認するための関数です。
ローカル変数 result に詳細な名前などが設定されているので、こいつらを変更してやります。
- Name : プラグイン名
- Match Name : After Effects が内部でコールする際の名前?
- Category : エフェクトの収納カテゴリー名
...
result = PF_REGISTER_EFFECT(
inPtr,
inPluginDataCallBackPtr,
"color_manager", // Name
"Scald color_manager", // Match Name
"Scald", // Category
AE_RESERVED_INFO
);
...
Skeleton_Strings.cpp
次に Skeleton_Strings.cpp を開きます。
グローバル変数に g_str というものがあります。
これはエフェクトプラグインの情報部分にあたるもので、以下のように表示されるテキスト部分を設定しています。
辿っていくと分かりますが、厳密にはSkeleton.cppのAbout関数で定義しているようですが、こうして別ファイルで定義しておけば書き換えがスムーズになるという意図があるのでしょう。
下の二つ ( GainとColor ) に関してはエフェクトコントロールに表示されるテキストの名前を定義しています。
これに関しても上で語っているものと同様です。
Skeletonテンプレートではこのような手法をとっているようです。
まあ適当にそれっぽく書いておきます
...
TableString g_strs[StrID_NUMTYPES] = {
StrID_NONE, "",
StrID_Name, "color_manager",
StrID_Description, "金とか買っちゃって~",
StrID_Gain_Param_Name, "Gain",
StrID_Color_Param_Name, "Color",
};
...
これでOK!
と思いきやまだ一段階あるようです、、、
はぁ〜〜〜〜(クソデカため息)
SkeletonPiPL.r
お次は Resources ディレクトリ内の SkeletonPiPL.r を開きます。
ここにきてなぜR言語?
と思った方も多いことでしょう。
( 正直自分も思いました笑 )
ここに関してはよくわかりませんが、おそらく After Effects の構造上Rのリソースファイルで読み込むことが都合が良いのでしょう。
まああまり深く考えることでもないのでそっとしておいてやります笑
中身の16行目あたりから20行目と、60行目あたりの部分を先ほどのSkeleton.cppの時と同様の値を設定してやります。
...
/* [2] */
Name {
"color_manager"
},
/* [3] */
Category {
"Scald"
},
...
/* [11] */
AE_Effect_Match_Name {
"Scald color_manager"
},
...
これでようやく名前の変更が終わりました。
いやめんどくさすぎるやろ。。。
メインプログラムのコーディング
メインプログラムをコーディングする前に各関数の処理内容を説明したいと思います。
まず Skeleton.cpp を開きます。
それぞれの関数の役割
色々とややこしそうな関数軍がありますが、それぞれ以下のような役割を担っています。
PluginDataEntryFunction - プラグインエントリーポイント
EffectMain - AEからコールされた際に毎回呼ばれるメイン関数
| About - プラグインの概要を設定
| GlobalSetup - 互換性などの設定
| ParamsSetup - エフェクトコントロールのUIを設定
└─ Render - メインの画像処理
└─ MySimpleGainFunc16 および 8 - それぞれのカラービット数に応じた画像処理
他にもout_flagsフラグなどを立てることで処理内容は変わったりしますが、今回は扱わないことにします。
Render関数の引数リスト
ここでにメインの画像処理を行いたいので Render 関数に移動します。
Render関数の引数は以下のような内容で構成されています。
- in_data : 入力画像の詳細情報 ※画像データではない
- out_data : 出力画像の詳細情報 ※こちらも画像データではない
- params : UIで設定されたパラメータの構造体ポインタ
- output : 出力画像データのポインタ
ややこしいのがin_dataやout_dataは入出力画像の詳細情報の記された構造体ポインタであり、画像データその物ではないという点です。
反対にoutputが出力画像データのポインタです。
つまりin_data、out_dataから画像の縦横幅などを取得し、その情報をもとにforループなどでoutputの出力画像の行列データを処理していくことになります。
まああまり言葉で書き綴ってもわかりづらいだけなので実際にプログラムをコーディングしながら説明していきます。
イテレータとMySimpleGainFunc
なんかごちゃごちゃと書かれていますが、ざっくり説明すると「AEGP_SuiteHandler」クラスを用いてそれぞれ8、16カラービットの配列をイテレータを利用してMySimpleGainFunc8, 16に処理させているといった具合です。
ですがとても見にくいのでローカル変数errとreturn部分を除いて中身を一度空にしてしまいます。
errとは処理中に何かしらの不具合が起きたときに万が一スタックオーバーしないようにAEに知らせるために設定するフラグです。
MySimpleGainFunc8, 16も消してしまって構いません。
...
static PF_Err Render(
PF_InData *in_data,
PF_OutData *out_data,
PF_ParamDef *params[],
PF_LayerDef *output
) {
PF_Err err = PF_Err_NONE;
return err;
}
...
入力画像の取得
勘の良い方はお気づきかと思いますが、この状態では入力画像データがありません。
なので取得してあげます。
取得方法はいたって簡単で引数のparams配列に0を指定し、中身のレイヤーデータをポインタ変数で保管してあげます。
uとかldとかその辺はおまじないとしておきます。
...
PF_Err err = PF_Err_NONE;
PF_LayerDef* input = ¶ms[0]->u.ld;
return err;
...
必要なデータの割り当てと色深度分け
まずそれぞれ必要なデータをわかりやすくローカル変数にまとめます。
...
PF_Err err = PF_Err_NONE;
PF_LayerDef* input = ¶ms[0]->u.ld;
int w = input->width,
h = input->height,
r = input->rowbytes;
if (PF_WORLD_IS_DEEP(output)) {
PF_Pixel16* src = (PF_Pixel16*)input->data;
PF_Pixel16* dst = (PF_Pixel16*)output->data;
r /= sizeof(PF_Pixel16);
}
else {
PF_Pixel8* src = (PF_Pixel8*)input->data;
PF_Pixel8* dst = (PF_Pixel8*)output->data;
r /= sizeof(PF_Pixel8);
}
return err;
...
wとhはそれぞれ画像の横幅(width)と縦幅(height)です。
rは画像データが格納されている配列の幅です。
srcとdstにそれぞれ入出力画像データにアクセスする構造体ポインタをキャストしておきます。
PF_WORLD_IS_DEEPマクロは現在のカラービット数が16ビットの場合にtrueを返します。つまり16ビットの場合はPF_Pixel16のサイズ、8ビットの場合はPF_Pixel8のサイズで割ってあげる処理になっています。
PF_Pixel~は1つの画素が格納されている構造体です。
8, 16, 32それぞれ red, green, blue, alpha の4チャンネル分格納できる構造体です。
鉄板の2重ループ処理
画像処理と言ったらやっぱり2重ループ!
基本的には列と行をそれぞれfor文でループするのが鉄板なのではないでしょうか!(タブン)
ということでfor文の2重ループで入力画像の画素にアクセスし、同じ位置の出力画像の画素に割り当てていきます。
画像処理に慣れている方からすれば当たり前の話ですが、画素へのアクセスには一癖あります。
例えば 3列 4行目 の画素にアクセスするには以下のように記述します。
src[4 + 3 * r]
今回はRGBの順番を入れ替える操作をしたいので、redをblue, blueをred で割り当てます。
...
PF_Err err = PF_Err_NONE;
PF_LayerDef* input = ¶ms[0]->u.ld;
int w = input->width,
h = input->height,
r = input->rowbytes;
if (PF_WORLD_IS_DEEP(output)) {
PF_Pixel16* src = (PF_Pixel16*)input->data;
PF_Pixel16* dst = (PF_Pixel16*)output->data;
r /= sizeof(PF_Pixel16);
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
dst[x + y * r].red = src[x + y * r].blue;
dst[x + y * r].green = src[x + y * r].green;
dst[x + y * r].blue = src[x + y * r].red;
dst[x + y * r].alpha = src[x + y * r].alpha;
}
}
}
else {
PF_Pixel8* src = (PF_Pixel8*)input->data;
PF_Pixel8* dst = (PF_Pixel8*)output->data;
r /= sizeof(PF_Pixel8);
for (int y = 0; y < h; y++) {
for (int x = 0; x < w; x++) {
dst[x + y * r].red = src[x + y * r].blue;
dst[x + y * r].green = src[x + y * r].green;
dst[x + y * r].blue = src[x + y * r].red;
dst[x + y * r].alpha = src[x + y * r].alpha;
}
}
}
return err;
...
ビルドの前に
プリプロセッサの定義
さて、これで一応の処理は書き終えました!
早速ビルドしてみましょう...とその前に、
Skeleton.h を開き、#pragma once の後に次の定義を書き加えておきます。
...
#pramga once
#define WINDOWS_IGNORE_PACKING_MISMATCH
...
これを書き加えないとビルド時にセキュリティチェックが働きビルドできない場合があります。
本来は何かしら対策をするべきなのですが、そもそもSDKのテンプレートがそのような対策をとっていないようなのでどうしようもありません。
環境変数の設定
After Effects のSDKではデフォルトでAE_PLUGIN_BUILD_DIRという環境変数を抽出場所として設定されています。
なのでご自身の環境変数に追加してやる必要があります。
( まあそれか単にAEのプラグインディレクトリを自力で指定してやっても良いです )
システム環境変数にAE_PLUGIN_BUILD_DIR変数を追加し、ディレクトリパスをAfter Effects のプラグインディレクトリを指定して保存します。
例:C:\Program Files\Adobe After Effects 2022\Support Files\Plug-ins\
※環境変数の設定方法は割愛します。
※場合によってはVisualStudioを再起動させる必要があります。
ビルド
それではプログラムをビルドしてみます。
無事にビルドできたら After Effects を起動します。
( ここでVisualStudioでデバッガをAEに設定してあげると楽に起動できます )
起動したら適当に画像などを読み込んでおきます。
エフェクトメニューを開くと、先ほど設定したカテゴリー名でメニューが追加されて無事エフェクトが認識されました。
ではこいつを適用してやりましょう!
すると無事色素の順番が入れ替わったエフェクトが適用されました。
おわりに
今回書いたプログラムはGitHubにて公開してますので、良ければそちらも参考にしてください!
また、より深い知識や既存のクラスなどを活用したプログラムを書きたい場合はこちらのガイドを参考にしてみてください。
またやる気が起きたらもうちょっと深い部分を書いてみるかもです~
( 超絶気分屋なのであんまり期待しないでください笑 )
お疲れさまでした!