DaVinci Resolve Fuse開発に関するメモ
Fuseとは?
概要っぽいもの
Fusionでは各種ツールを組み合わせることで画像処理が可能。
Mergeツールで画像を重ねたり、Transformツールで回転や拡大縮小したり。
そういう画像処理ツールを自作したものがFuseプラグイン。
あとモディファイアも作れるらしい。
使用言語はLua。
変更を加えたらすぐにリロードして試せるので、開発しやすい。(基本的には変更する度にアプリケーションの再起動をする必要はない)
参考資料
このスクラップは下記資料を参考に記述しています。
注意点
このスクラップでは上記資料を完全に翻訳したり、全ての要素を漏れなく記述するものではありません。なので「ここで書いてないから存在しない」と早合点しないように注意してください。
普通に記述漏れとかありますので、完全な情報を知りたい方は各自で調査したり公式資料を翻訳してください。
Fuseの公式テンプレート
Windowsの場合、テンプレートは下記フォルダにあった。
「C:\ProgramData\Blackmagic Design\DaVinci Resolve\Support\Developer\Fusion Fuse」
基本的にDeveloperフォルダの下を探せば良いと思われる。
Fuseを使うには
Fuseファイルを指定のフォルダに配置する
Windowsの場合、Fuseファイルを下記のフォルダに配置すると書かれているけど、反映されなかった。
「C:\ProgramData\Blackmagic Design\DaVinci Resolve\Support\Fusion\Fuses」
(そもそもSupportフォルダの下にFusionフォルダがない)
パスマップを確認したところ、反映されるFusesフォルダはデフォルトだと3つくらいある。
- C:\Users\ユーザ名\AppData\Roaming\Blackmagic Design\DaVinci Resolve\Support\Fusion\Fuses
- C:\ProgramData\Blackmagic Design\DaVinci Resolve\ Fusion\Fuses
- C:\Program Files\Blackmagic Design\DaVinci Resolve\ Fusion\Fuses
(パスマップはFusionページに切り替えて、上部メニューから「Fusion」→「Fusion設定」でFusion設定画面を開き、「パスマップ」の項目で確認できる。
一覧から「Fuses」の行を右クリックすると該当フォルダパスが表示されるので、そのパスをクリックすると該当フォルダを開くことができる)
Fuseを使うために配置するフォルダはパスマップに含まれているところであれば、どのフォルダでもOK。
今回はその内の一つ、下記のフォルダにコピーしたところ無事に読み込むことができた。
「C:\ProgramData\Blackmagic Design\DaVinci Resolve\Fusion\Fuses」
アプリケーションを再起動する
Fuseファイルはアプリケーションを起動するタイミングで読み込まれる。
なので、指定フォルダにFuseファイルを配置した際、アプリケーションを既に起動していたなら再起動する。
無事に読み込めていれば、画像のようにFusionページでツール選択できるようになっている。
Fuseで記述する各イベント処理
Fuseの記述は以下の関数とイベント処理に分けられる。
基本的に上に位置するほどイベントの処理タイミングが早い。
なお、必ず定義する必要があるのは以下の3つ。
- FuRegisterClass
- Create
- Process
FuRegisterClass(name, ClassType, attribute):必須
- アプリ起動時に実行される関数(イベント処理ではない)。
- そのため、ここを変更したときだけはアプリの再起動が必要になる。
- ツール名や説明、分類カテゴリなどの基本情報の設定。
- 場合によっては複数定義可能。
Create():必須
- ツールをFusionコンポジションに追加したときのイベント処理。
- イメージの入力と出力、その他コントロール(GUI)の設定。
OnAddToFlow()
- ツールをフロー内に配置したときのイベント処理。
- モディファイアはコントロールに追加された時に処理される。
- 配置したコンポジション毎、あるいは配置したタイミングの再生フレームなどに応じて初期化したい場合に処理を記述する。
- 例:フレームタイミングを調整したいので配置したコンポジションのFPSを取得するなど
OnConnected(link, old, new)
- ツールの入力へ接続があった際に呼び出されるイベント処理。
- ノードのイメージ入力だけでなく、コントロールへのモディファイア追加でも処理される。
NotifyChanged(inp, param, time)
- コントロールの値を変更したときのイベント処理。
- キースプラインやモディファイアなどで値が変化したときも処理される。
- 他のコントロールの操作(コントロール間の連動)も行うことが可能。
CheckReqest(req)
- Processより前に処理される。
- コントロールからの値を上書きしてProcessに渡すことも可能で、不要なレンダリングを回避するなどの用途もある。
PreCalcProcess(req)
- Process()関数が呼び出される前に処理される。
- 予想される画像サイズ、DoDなどをFusionに伝えるために使用される。
- 基本的に使用する必要はないものの、特定のケースにおいてはカスタムする必要がある。
- 例えば、画像の拡大縮小したりマージしたり等、入力と出力で最適なDoDが異なる処理をする場合など
Process(req):必須
- メイン処理はここに記述。
FuRegisterClass(string name, enum ClassType, table attribute)
実行タイミング
アプリの起動時、Fuseツールを初めて読み込むときに実行される。
そのため、FuRegisterClassに変更を加えた場合はアプリの再起動が必要となる。
引数
-
- Fusionが識別するためのプラグインID。他のFuseと被ってはいけない。
- 後述のREGS_Name属性に指定がなければFuseの名前としてそのまま使われる。
- 渡せる文字列には英数字とアンダーバー( _ )のみ使用可能。
- Createイベントなどで参照したいときは「self.RegNode.m_ID」で取得可能
- ただし、この方法で取得すると「Fuse.name」のように先頭に「Fuse.」が付く
- comp:GetToolList()で該当Fuseを絞り込んで検索したい場合、"Fuse.name"というように「Fuse.」を含めて渡すと良い
- ただし、この方法で取得すると「Fuse.name」のように先頭に「Fuse.」が付く
-
- CT_Tool : ノード
- CT_Modifier : モディファイア
- CT_ViewLUTPlugin : ビューLUT
-
次の項目で説明。
必須属性
-
- ツールが表示されるカテゴリを設定する文字列値。
- 「Script\Color」というように記述すると、Scriptカテゴリの下にColorカテゴリを作成する。
- ツールを分類するときに設定することになる。
-
- ツール名の略語。ツールバーメニューとビンで使用される。
-
- Fusionインターフェースの様々な部分で使用されるツールの簡単な説明。
全タイプ共通のオプション属性
-
- Fuse名
- FuRegisterClassの引数nameはID(種類)として運用し、名前が別途必要なときに設定
- 例:Number型とPoint型の両方に対応したモディファイアを作成する場合、nameは別に設定し、REGS_Nameは同じ名前にする等
- 設定値は「self.RegNode.m_Name」で取得可能
-
- カレントフレームの変更時に処理を行うか
- false だとFuseをリロードしたりモディファイアを適用したタイミングのように、Fuseを読み込んだタイミングに設定されていたフレームを参照して1回だけ処理している?
- 再生時間に合わせてアニメーションさせたいなら true にしとけばOKな模様
- ただし、true にすると毎フレーム処理を行うので描画負荷が高くなる点に注意
-
- ヘルプを記載したファイルやWebページのURL
-
- 作成者のページなどのURL?
-
- 編集ボタンの非表示フラグ
- 編集ボタンを押すと「Fusion設定」で設定されたスクリプトエディタで該当Fuseファイルを開く
赤枠内に設定したエディタで開く
-
- リロードボタンの非表示フラグ
- Fuseを編集したら、リロードボタンを押すことで反映される
(FuRegisterClassのみ、アプリの再起動が必要)
Fuseの編集ボタン、リロードボタン
-
- バージョン情報
-
- PreCalcProcessイベント実行の際、Processイベント用の処理を呼び出すようになる
(ただし、PreCalcProcess用の関数が定義されている場合、PreCalcProcess用の関数を優先する) - PreCalcProcessとProcess、どちらで処理中かはProcessイベント内でRequest.IsPreCalc()で判別可能
- PreCalcProcessイベント実行の際、Processイベント用の処理を呼び出すようになる
ノード作成時のオプション属性
-
-
- 対応するコントロールの種類
- 'Image' : 画像を扱うコントロール
- 対応するコントロールの種類
-
- マスク用の画像Inputを表示し、処理に使えるようにする
-
- ブレンドできるかどうか?
-
- オブジェクトマットからのマスキングが出来るかどうか?
-
- モーションブラーを使用できるかどうか?
-
- エフェクト欄で表示された際のアイコンID。
- "Icons.Tools.Icons.Dissolve" という風に指定すると、該当する既存ツールのアイコンを割り当てることが可能な模様。
-
- タイル表示したときに表示される画像をBitmap形式の文字列で設定できる。
VFXPedia の Switch.Fuseの場合 - サイズは最大で幅160*120pxまで指定可能
- 設定時のtableは以下の3つのキーを持つこと。
- Width : int (1~160)
- Height : int (1~120)
- Bitmap : string
- RGBAの値をそれぞれ2桁の16進数に変換し、8桁の文字列で1つのピクセルとする
- 横方向は半角スペースで区切り、縦方向は改行で区切る
- 実物を見た方が早いので、VFXPediaのFuse例のコードを参照することをオススメする
VFXPedia の TilePic.Fuseのコード
- タイル表示したときに表示される画像をBitmap形式の文字列で設定できる。
モディファイア作成時のオプション属性
-
-
- 対応するコントロールの種類
- 'Text' : 文字列を扱うコントロール
- 'Number' : 数値を扱うコントロール
- 'Point' : 座標を扱うコントロール
- 複数のコントロールに対応することも可能
- 例:Number型とPoint型の両方に設定可能なモディファイアを作成したい場合、一つのFuseファイルにNumber型用とPoint型用の2つのFuRegisterClassを定義すればOK
- 例:Number型とPoint型の両方に設定可能なモディファイアを作成したい場合、一つのFuseファイルにNumber型用とPoint型用の2つのFuRegisterClassを定義すればOK
- 対応するコントロールの種類
コントロールの追加について
基本的にはCreateイベントでコントロールを設定する。
ネストなどの一部を除き、既存ノードに追加できるユーザコントロールと設定できる項目は大体同じ。
MultiMergeノードのように入力イメージの数に応じて入力コネクタを増やしたい場合、OnConnectedイベントで追加するのが良い。例:Switch.fuse
コントロールの追加方法
入力はself:AddInput()関数、出力はself:AddOutput()関数で追加する。
ほとんどのケースにおいてOutputは一つは設定することになる。
(画像をRGB値で分けたり、複数設定するケースもある)
self:AddInput(labelname, scriptname, attributes)
self:AddOutput(labelname, scriptname, attributes)
-
- コントロールの横に表示されるラベルの文章。利用者がわかりやすいものを付ける。
- Input:GetAttr("LINKS_Name") で取得可能
- Input:SetAttrs({LINKS_Name = "好きな文章"}) で変更可能
-
- スクリプトから参照するための名前。例えばLoaderノードのFileControlは「Loader1.Clip」で参照できるように「Clip」というscriptnameが付けられている。
- Input:GetAttr("LINKID_ID") で取得可能
-
- 各コントロールの種類に応じた属性を格納したテーブル。コントロールで指定できる最小値・最大値など、内容は様々。
- Input:GetAttr(attr_name) で取得可能(引数は取得したい属性名)
- Fusionスクリプトと異なり「GetAttrs」 ではない点に注意(末尾に s が付かない)
- 引数を省略して属性設定値一覧を取得、ということができないため
- Fusionスクリプトと異なり「GetAttrs」 ではない点に注意(末尾に s が付かない)
- Input:SetAttrs({attr_name1 = value1, attr_name2 = value2, ...}) で変更可能
- 属性名をキー(attr_name)に、設定値(value)を格納したテーブルを引数とする
- ほとんどのコントロールに共通する汎用的な属性についてはFusionSDKの49~50Pに記載
実際の追加は下記のように行う。
(DaVinci Resolveのサンプル「Example 1 Bright Contrast.fuse」から抜粋。)
function Create()
InBright = self:AddInput("Brightness", "Brightness", {
LINKID_DataType = "Number",
INPID_InputControl = "SliderControl",
INP_MaxScale = 1.0,
INP_MinScale = -1.0,
INP_Default = 0.0,
})
InContrast = self:AddInput("Contrast", "Contrast", {
LINKID_DataType = "Number",
INPID_InputControl = "SliderControl",
INP_MaxScale = 1.0,
INP_MinScale = -1.0,
INP_Default = 0.0,
})
InSaturation = self:AddInput("Saturation", "Saturation", {
LINKID_DataType = "Number",
INPID_InputControl = "SliderControl",
INP_MaxScale = 5.0,
INP_MinScale = 0.0,
INP_Default = 1.0,
ICD_Center = 1,
})
InImage = self:AddInput("Input", "Input", {
LINKID_DataType = "Image",
LINK_Main = 1,
})
OutImage = self:AddOutput("Output", "Output", {
LINKID_DataType = "Image",
LINK_Main = 1,
})
end
コントロールの種類(コネクタ)
LINKID_DataType属性で指定する。
- Image:画像入力。AddInputで指定すると入力用コネクタが、AddOutputで指定すると出力用コネクタが追加される。
コントロールの種類(インスペクタ)
LINKID_DataType属性で指定する。
コントロールの種類 | 概要 |
---|---|
ImageControl | Wireless Linkノードのようにコネクタを介さずに画像入力できる。 ノード名を入力するか、ノードをドラッグ&ドロップで設定する。 |
ButtonControl | ボタン。 クリックするとBTNCS_Execute属性に設定されたコマンド(文字列で設定)を実行する。また、NotifyChangedイベントが2回連続発生する。 (引数paramが1回目は0→1、2回目は1→0に変化) リロードなど、ユーザに任意のタイミングで処理をさせたいときに使用。 |
CheckControl | チェックボックス。 |
ColorControl | カラー設定用コントロール。 スライダーやボタンなどのセット。設定項目(RGBA等)の数だけ追加する必要がある。 全項目を追加する必要はなく、例えば赤のスライダーだけ追加しても良い。 |
ComboControl | コンボボックス。 |
FileControl | ファイル選択用。 ダイアログ表示用のボタンも付く。 |
FontFileControl | フォント選択用コンボボックス。 端末にインストールされているフォントの一覧を選択できる。 |
GradientControl | グラデーション設定用。 白~黒のスライダー。 |
LabelControl | ラベル。 情報表示用。 ラベルの表示文字列はTextEditControlのように値として保有しているわけではないため、LabelControl:SetSource("変更文字列", 0) では設定できない。LabelControl:SetAttrs({LINKS_Name="変更文字列"}) で変更すること。 |
MultiButtonControl | マルチボタン。 押されているボタンは0~ボタン数-1の数値で判別可能。 |
OffsetControl | X座標とY座標の2つの入力欄を持つ。 主に座標設定に用いる。 |
RangeControl | 2つのつまみを持つスライダー。 主に範囲の設定に用いる。 |
ScrewControl | 最小値も最大値もない無限のスライダー。 回転角度のように上下限がない数値の設定向き。 |
SliderControl | スライダー。 主に一定範囲内の値の設定に用いる。 |
TextEditControl | テキストボックス。 行単位で高さを調整できるため、1行分の簡単な入力ボックスから長文入力用のボックスなども設定できる。 |
コントロールの種類(OnScreen UI Widgets)
他のコントロールに追加設定する画面コントロール群。
追加すると座標や角度など、UI上で直感的に設定することが可能。
INPID_PreviewControl属性で指定する。
ウィンドウ中央の矢印や点線の円がOnScreenコントロール
コントロールの種類 | 概要 |
---|---|
PointControl | 1点を示す小さい■。座標の設定用。 |
AngleControl | 円と角度を示す線を持つ。角度の設定用。 |
CrosshairControl | ↑と→のクロスヘアを持つ。座標などの設定用。 |
RectangleControl | 四角形を表示。切り抜きなど、画像処理したい範囲の設定向き? |
ネストの設定(インスペクタ)
ネストの開始位置を self:BeginControlNest(labelname, scriptname, open_state)、
ネストの終了位置を self:EndControlNest() で指定する。
self:BeginControlNest() の第3引数はネストの初期状態が開いているかどうかをbool値で設定する。
デフォルトはtrue(ネストが開いた状態)。
下記のようにネストに格納したいコントロールを self:BeginControlNest() と self:EndControlNest() で挟んで使う。
self:BeginControlNest("ネストラベル", "TestNest", false) -- ネストの開始(第3引数は初期状態で開くかどうか)
InSlider = self:AddInput("スライダー", "SliderInput", {
LINKID_DataType = "Number",
INPID_InputControl = "SliderControl",
INP_MaxScale = 6.0,
INP_MinScale = 1.0,
INP_Default = 1.0,
IC_Steps = 1.0,
})
self:EndControlNest()
複雑な構造のネストを作成する
self:BeginControlNest() と self:EndControlNest() の間でネストを追加する関数を実行した場合、入れ子構造になる。
function nest2_add()
nest2 = self:BeginControlNest("Nest2", "Nest2", true, {})
InLabel3 = self:AddInput("label3", "Label3", {
LINKID_DataType = "Text",
INPID_InputControl = "LabelControl",
})
self:EndControlNest()
end
function Create()
nest1 = self:BeginControlNest("Nest1", "Nest1", true, {})
InLabel1 = self:AddInput("label1", "Label1", {
LINKID_DataType = "Text",
INPID_InputControl = "LabelControl",
})
InLabel2 = self:AddInput("label2", "Label2", {
LINKID_DataType = "Text",
INPID_InputControl = "LabelControl",
})
nest2_add() -- 関数でネスト追加
self:EndControlNest()
end
つまり、再帰関数によるネストの設定が可能になる。
これを利用すると、複雑な階層のネストも作りやすくなる。
ページの設定(インスペクタ)
ページの追加は self:AddControlPage(pagename) で、
ページの削除は self:RemoveContorolPage(pagename)で可能。
新規コントロールは直前に追加されたページに追加される。
新規コントロールを別のページに追加したい場合、既存ページの名前を引数に渡して実行すると切り替えられる。
self:AddControlPage()でできることまとめ
実施したい処理 | 実施方法 |
---|---|
新規ページの追加 | 第1引数を既存ページとは異なる名前にして実行 |
既存ページへの切り替え | 第1引数を切り替えたい既存ページと同じ名前にして実行 |
既存ページを非表示にする | 第1引数を非表示にしたい既存ページと同じ名前に、 第2引数を {CT_Visible = false} にして実行 |
既存ページを再表示する | 第1引数を再表示したい既存ページと同じ名前に、 第2引数を {CT_Visible = true} にして実行 |
ちなみにFuseにはデフォルトで「コントロール」ページがあるが、下記のように「コントロール」ページを削除したり非表示にすることで最初のページを変更できる。
(アプリ上はページ名が「コントロール」だが、ページを非表示にするときは「Controls」で指定すること)
-- self:RemoveControlPage("Controls") -- コントロールページの削除
self:AddControlPage("Controls", { CT_Visible = false }) -- コントロールページを非表示
self:AddControlPage("新ページ")
デフォルトだと「コントロール」ページが一番左にある
「コントロール」ページを非表示にして、追加ページを一番左にした
コントロールと出力の値の取得・設定方法
スクリプト、およびフレームレンダースクリプトとはやり方が違うので注意が必要。
コントロール値の取得方法
Input:GetValue(req)
レンダリング要求が来た時点の値を取得できる。
引数はProcessイベント関数に引数として渡されるRequestオブジェクトをそのまま渡して使用する。そのため、Processイベント関数でのみ使える。
Input:GetSource(time)
指定した時間(フレーム)の状態を取得できる。
NotyfiChangedイベントなど値を取得したい場合や、前後の数フレーム分の値を参照して計算したい場合などに用いる。
基本的にキースプラインなどでアニメーションさせない設定の場合、 Input:GetSource(0).Value という風に値を取得すると良い。
アニメーションしている場合、引数timeに適切な値を渡すこと。
コントロール値の設定方法
Input:SetSource(value, time) で指定した時間(フレーム)に値を設定できる。
第1引数と第2引数の順番を間違えないように注意。
値設定時のデータ型
第1引数に渡せるのはText、Number、Pointの3種類。
いずれもFuseで定義されてるオリジナルクラス(型?)なので、第1引数に渡す際は Text("str value") という風に変換する必要がある。
もしText型に変換せずにstring型のまま、あるいはNumber型に変換せずにint型のまま引数へ渡したりした場合、値が反映されないので注意すること。コンソールにもエラーが出ないので気が付きにくい。
基本的にキースプラインなどでアニメーションさせない設定の場合、 Input:SetSource(Text("text"), 0)という風に値を設定すると良い。
アニメーションさせる場合、引数timeに適切な値を渡すこと。
出力(Output)の値の設定方法
Output:Set(req, value) で設定できる。
第1引数のreq (Reqestクラス)はProcessイベント、PreCalcProcessイベントハンドラへ引数として渡されるものなので、Outputの値設定は実質的にこの2つのイベント専用。
Input:SetSource()のように時間を指定して値を設定することはできないので注意。
以下は固定値を設定する簡単な例。
function Process(req)
print("再生フレーム:" .. req.Time)
OutValue:Set(req, Number(1))
end
メモ(後でコントロールの属性、あるいはトラブルシューティングにまとめます)
コントロールには INP_Required 属性がある。デフォルトは true。
INP_Required = true の場合、該当する全コントロールの値が設定されていないとProcessイベントが処理されない。
NotifyChangedなどの他のイベントは処理されるのにProcessイベントが処理されない場合、コントロールの値の設定忘れがないか確認したり、設定が必須ではないコントロールは INP_Required = false に設定し忘れていないか確認すること。
メモ(後でコントロールの属性、あるいはトラブルシューティングにまとめます)
AddInput()でイメージ入力コネクタを追加した場合、何かノードを接続しないとエラーを吐いてレンダリングができなくなることがある。
(Processイベント処理でコネクタからの入力を使用しているかは関係ない模様)
ノードにイメージ入力コネクタがある場合、特に何も指定していないとProcessイベントの前のPreCalcProcessイベントで上流ノードを元にDoD計算などの処理を行おうとするため、コネクタ接続がないと存在しない上流ノードを処理しようとしてエラーを吐く模様。
対策としてはPreCalcProcessイベント関数を記述する。
あるいはFuRegisterClass関数で REG_NoPreCalcProcess = true を設定し、Processイベント関数内にPreCalcProcessイベント用の処理を記述する。
下記は後者の例。
if req:IsPreCalc() then -- 事前計算
local img_precalc = Image({IMG_Like = out, IMG_NoData = true}) -- ダミーデータ生成
OutImage:Set(req, img_precalc)
else
OutImage:Set(req, out)
end
画像ファイルから画像を読み込む方法
Loaderノードのように選択した映像メディアを読み込むにはClipオブジェクトを使う。
Clipオブジェクトのプロパティ
画像を読み込む例
function Process(req)
local clip = Clip(filename, false) -- 画像ファイル読み込み設定
clip:Open()
local img = clip:GetFrame(0) -- 画像フレーム読み込み
clip:Close()
OutImage:Set(req, img) -- 画像出力
end
Clip(string filepath, boolean save, FusionDoc doc)
- filepath:読み込むファイルパス
- save:存在しないパスを許容するか? 読み込むときはfalseで良い。
- doc:よくわからず。
Clip:GetFrame(int frame)
- frame:取得するフレーム番号
事前にClip:Open()を実行しておくこと。
また、Loaderノードのように画像ファイル名末尾が数値の場合、シーケンシャル(連番)ファイルとして認識されるため、適切なフレーム番号を指定する必要がある。
シーケンシャルファイルではない場合、0を指定しておけば特に問題はない。
Clip:PutFrame(int frame, Image img)
- frame:出力する際のフレーム番号?
- img :出力するイメージ
事前にClip:Open()を実行しておくこと。
イメージを保存する。(※実際の挙動は確認していません)
Clip:Open()
指定ファイルを開き、画像の取得や保存を可能とする。
Clip:Close()
指定ファイルを閉じる。必要な処理が終わったらちゃんとクローズすること。
シーケンシャルファイルの画像をアニメーションさせずに表示する方法
正規表現でファイル名末尾の数値を取得し、Clip:GetFrame()の引数に渡せばOK。
表示フレームはFileControlのNotifyChangedイベントで予め抽出しておき、self:SetData()で保存、self:GetData() でProcessイベントから参照する方法もある。
function Process(req)
local filename = self.Comp:MapPath(InFile:GetValue(req).Value) -- ファイルパス取得
local frame = tonumber(filename:match(".?(%d+)%.%w+$")) -- ファイル名末尾の数値取得
if frame == nil then -- シーケンシャルファイルではない場合、0を指定
frame = 0
end
local clip = Clip(filename, false) -- 画像ファイル読み込み設定
clip:Open()
local img = clip:GetFrame(frame) -- 画像フレーム読み込み
clip:Close()
OutImage:Set(req, img) -- 画像出力
end