見出し画像

After Effects エフェクトプラグインをゼロから作って見よう 5回目 "SDK"

そういえば4回も投稿してるのに肝心のSDKの事説明していなかったことに気が付いて、今回はSDKの解説です。どう考えても順番違うなぁ。

SDKって何?

SDKとは、Software Development Kitって意味になります。まぁ、そのソフトを作るためにそのソフトを作ってる組織が情報・サンプル等を集めたものですね。After Effectsではプラグインを作るためのプログラム用のヘッダーやそのサンプルコード・ドキュメントがまとまっています。
昔はインストール用のCD-ROMのおまけで入っていましたが、最近はAdobeIDを獲得してWebからのダウンロードで手に入れることができます。
インストールすると"AfterEffects SDK"ってフォルダにサンプルとガイドのPDFが入ったフォルダがインストールされます。バージョンがいろいろあるのでフォルダ名に"2023"とか入れてリネームしてます。

ガイドのPDFよりオンラインのガイドの方がブラウザの機能で翻訳ができるので僕はもっぱらオンラインです。

フォルダ内

プラグイン作成に必要なヘッダー類は以下のフォルダに入ってます。
その他はサンプルコードです。かなりざっくばらんに収納されてます。

  • headers 主なCPP用のヘッダーが愛ってる

  • Util なんで分かれてるのかわからないけど、これにもヘッダーが入ってる

  • Resources PiPLコンパイラの"PiPLtool.exe"とそれ用のヘッダー

  • Effect エフェクトプラグイン用のサンプルコード

それ以外はまぁ興味があったら見る程度でいいでしょう。僕は見たことないです。
後、ぽこっとPDFなんかがおかれてることありますが、たいていはこのバージョンから追加された新機能の解説書で、たまにこれにしか解説なかったりすることあるので、可能な限り違うバージョンのSDKをDLして確保しておくとのちのち役に立ちます。

SDKとしてはバージョン3.1が一番わかりやすかった記憶ありますが、僕自身紛失して持ってないです。まぁMac専用でしたが。

主なヘッダー

目を通しておいたほうが良いヘッダーは

A.h

そっけないファイル名ですが、SDKで使われている変数の宣言が行われています。SDKでは普段使う型を"A_"で始まる型になっています。コンパイラの変更時やプラットホームの変更時にコードの変更を最小限に抑えるための工夫ですね。

	typedef int32_t			A_long;
	typedef uint32_t		A_u_long;
	typedef char			A_char;
	typedef double			A_FpLong;
	typedef float			A_FpShort;
	typedef A_long			A_Err;
	typedef void *			A_Handle;
	typedef A_long			A_Fixed;
	typedef A_u_long		A_UFixed;

	#if defined( __MWERKS__) || defined (__GNUC__)  // metrowerks codewarrior and XCode/GCC
		typedef int16_t			A_short;
		typedef uint16_t		A_u_short;
		typedef uint8_t			A_u_char;
		typedef uint8_t			A_Boolean;
		typedef intptr_t		A_intptr_t;
	#else // windows
		typedef short			A_short;
		typedef unsigned short	A_u_short;
		typedef unsigned char	A_u_char;
		typedef unsigned char	A_Boolean;	
		#ifdef  _WIN64
			typedef __int64     A_intptr_t;
		#else
			typedef  int32_t       A_intptr_t;
		#endif
	#endif

AfterEffects.h

無茶重要なヘッダーです。バージョン関係の定数・マクロやoutflagsの宣言とか書いてあります。ドキュメント読むよりここを読んだ方がわかりやすいです。

Param_Utils.h

エフェクトのパラメータ登録に使うマクロが登録されてるヘッダーです。よく見ます。

AE_Macros.h

型変換等のマクロが定義されてます。見ておくとコーディングが楽になります。

上記がよく見るヘッダーです。

After Effects ホストの機能を使う Suite

プラグインはAfter Efefctsホスト本体が持つ機能をコールバック関数として呼び出して使えます。パラメータの登録とかですね。
"何とかSuite"って形で膨大な数の機能がまとめてつかえるようになっています。呼び出しが面倒なんですよね。

AEFX_SuiteScoper<PF_HandleSuite1> handleSuite = AEFX_SuiteScoper<PF_HandleSuite1>(	in_data,
																					kPFHandleSuite,
																					kPFHandleSuiteVersion1,
																					out_data);
ParamInfo	*infoP = reinterpret_cast<ParamInfo*>(handleSuite->host_lock_handle(reinterpret_cast<PF_Handle>(extraP->input->pre_render_data)));
	
if (infoP){
		ERR((extraP->cb->checkout_layer_pixels(	in_data->effect_ref, PARAM_INPUT, &input_worldP)));
		ERR(extraP->cb->checkout_output(in_data->effect_ref, &output_worldP));
		
		PF_PixelFormat		format	=	PF_PixelFormat_INVALID;
		
		AEFX_SuiteScoper<PF_WorldSuite2> wsP = AEFX_SuiteScoper<PF_WorldSuite2>(in_data,
																				kPFWorldSuite,
																				kPFWorldSuiteVersion2,
																				out_data);
		
		if (infoP->valF == 0.0) {
			err = PF_COPY(input_worldP, output_worldP, NULL, NULL);
		} else {
			ERR(wsP->PF_GetPixelFormat(input_worldP, &format));
			
			if (!err){

				AEFX_SuiteScoper<PF_iterateFloatSuite2> iterateFloatSuite =
					AEFX_SuiteScoper<PF_iterateFloatSuite2>(in_data,
															kPFIterateFloatSuite,
															kPFIterateFloatSuiteVersion2,
															out_data);

上記のコードは適当なサンプルからのコピペですが、エントリー関数の引数からSuite呼び出し用のクラス"AEFX_SuiteScoper"を作成して、そのメンバーとして関数を呼び出しています。
昔は"PF_COPY"のように直接呼び出していましたが、機能が増えてきてこのクラス経由で呼び出すことが増えてます。"PF_COPY"は以下のように定義されてます。

#define PF_COPY(SRC, DST, SRC_RECT, DST_RECT)	\
	(*in_data->utils->copy)(	\
		in_data->effect_ref, (SRC), (DST), (SRC_RECT), (DST_RECT))

AEGPプラグインというエフェクトじゃない機能追加用のプラグインは100%これらを使って制御してます。

前回の記事で作った"NF_Skelton"って基本的にそこら辺の面倒なことをカプセル化して画像処理のみをコーディングできるように作ってます。
F's のスケルトンがかなりカプセル化してて最近だと融通が利かなくなってきたので簡単にしたのが"NF_Skelton"です。

Fixed 固定小数点数

AEはもうかなり古いソフトでいろいろ古い仕様が残っています。
PC自体の速度が遅く特に浮動小数点数を避けて高速化のために多くのパラメータが固定小数点数になっています。A_Fixed/A_UFixedですね。

	typedef A_long			A_Fixed;
	typedef A_u_long		A_UFixed;

定義を見るとlong(32bit)の整数で、コンパイラで特別に定義されてる変数ではありません。詳しくは固定小数点数(クリックするとWikipediaに飛びます)を見てください。
コード時に上の16bitを整数部下の16bitを小数部として扱うだけなものです。以下はFixed関係の変換マクロです。

#define FIX2INT(X)				((A_long)(X) >> 16)
#define INT2FIX(X)				((A_long)(X) << 16)
#define FIX2INT_ROUND(X)		(FIX2INT((X) + 32768))
#define	FIX_2_FLOAT(X)			((A_FpLong)(X) / 65536.0)
#define	FLOAT2FIX(F)			((PF_Fixed)((F) * 65536 + (((F) < 0) ? -0.5 : 0.5)))

固定小数点の計算は加算減算は特に問題ないのですが、固定小数同士の掛け算(乗算)割り算(除算)が下駄をはかせた分の16bitも考慮しないと全然だめです。
それだけならまだしも計算結果が32bitを超えるとオーバーフローしてとんでもないことになります。
昔のプラグインのバグはこれが原因がほとんどでした。

僕はパラメータの計算は全てdouble(PF_fplong)に変換してしてます。昔と違って今のPCは浮動小数点数の計算がめちゃ早いので気にすることはないです。

覚えておきたい重要な構造体

PF_InData

After Efefctsホストの情報が詰まった構造体です。

PF_OutData

プラグインが行った処理をAEに伝えるための構造体です。

PF_ParamDef

いろいろなパラメータ情報です、プラグインにはこれの配列が渡されます。
特にインデックス0番は入力画像そのものです。

PF_EffectWorld/PF_LayerDef

AEから渡される画像データそのものです。
メンバのdataがピクセル配列の先頭ポインターになります。
PF_EffectWorld/PF_LayerDefって何故か二つあるのですが、実は同じものです。

横幅(width)と物理的なデータサイズが必ず違うのでアドレス計算する時は物理的な横幅のバイト長(rowbytes)から求めてください。

A_long w = input.width;
A_long wt = input.rowbytes / sizeof(PF_Pixel); 

PF_Pixel / PF_Pixe16 / PF_Pixel32(PF_PixelFloat)

これはPF_EffectWorldの画像構造体のピクセル構造体になります。
それぞれ8bit(Byte)/16bit(Short)/32bit(float)サイズになっています。
色変換方法は以下のマクロで行えます。

#define CONVERT8TO16(A)		( (((long)(A) * PF_MAX_CHAN16) + PF_HALF_CHAN8) / PF_MAX_CHAN8 )

何故か最近のSDKでは他のパターンが削除されてしまったのでNF_Skeltonでは以下のようにしています。

#define NF_CONVERT8TO16(A)		( (((A_long)(A) * PF_MAX_CHAN16) + PF_HALF_CHAN8) / PF_MAX_CHAN8 )
#define NF_CONVERT8TO32(A)      ((double)((long)((double)A*10000.0/(double)PF_MAX_CHAN8 + 0.5))/10000.0)
//#define NF_CONVERT8TO32(A)      ((double)(A)/PF_MAX_CHAN8)

#define NF_CONVERT16TO8(A)		( (((A_long)(A) * PF_MAX_CHAN8) + PF_HALF_CHAN16) / PF_MAX_CHAN16)
#define NF_CONVERT16TO32(A)      ((double)((long)((double)A*10000.0/(double)PF_MAX_CHAN16 + 0.5))/10000.0)
//#define NF_CONVERT16TO32(A)      ((double)(A)/PF_MAX_CHAN16)

#define NF_CONVERT32TO8(A)		(  (long)((A)*PF_MAX_CHAN8+0.5) )
#define NF_CONVERT32TO16(A)		(  NF_CONVERT8TO16(NF_CONVERT32TO8(A)) )
//#define FS_CONVERT32TO16(A)		( (long)(PF_MAX_CHAN16*(A)+0.5) )

#define NF_SHORT(A)				((double)(long)((double)A*10000.0 + 0.5)/10000.0)

32bitへの変換は適当な丸め込みを行っています。ここら辺はフィルタによっては変えています。
ピクセルの比較を行うときはいろいろ問題があるのですべて8bitに変換してやってます。コンパイラのバージョンによって誤差が出るのでまぁ8bitでいいかなと。

最後に

初めてプログラミングみたいな記事になりました。
でも、このマガジンの記事どう考えてもプログラム初心者向けじゃないですね。C++の説明一切なしですし^^;  まぁいいですよね。noteの購読層って全然わからないので、基本的に自分向けのメモとして書いてます。

次の記事は合成モードの話にします。特にalphaチャンネルを考量した時のコーディングについてです。これがわからないとほとんどのエフェクトがコーディングできないので。

この記事が気に入ったらサポートをしてみませんか?