After Effectsのスクリプトの可能性を広げる。 その7

github.com

型定義をTypes-for-Adobeのものに

AEスクリプト周りの型定義は個人的なものを使用していたが(Types-for-AdobeのAEの箇所の元になったもの)、ユーザーも散見されるようになり、そこらへんの指摘が入ったので切り替えた。

Mouseのhookをmouseupにも対応した

実際に使用される方が出てきて、その方の要望で追加した。

@cropped_mesh_warp

番外

SVGが公式で対応されるようになった

SVG ファイルを読み込み New Vector Workflow Enhancements in After Effects Beta | Community 公式がやるのならシェイプレイヤーに変換するのではなくて、まずsvgをaiのように一つのvector画像として扱えるようにすべきではある。複雑なものを扱うと重くなるし、そもそも静的な画像として扱いたい場合が大半だろうから。

追記

静的な画像としても扱えるようになったっぽい。

After Effectsのスクリプトの可能性を広げる。 その6

github.com

github.com

画像ファイルを介することなく気軽に画像データを扱いたいと開発当初より思っていて、やっと煮詰まった。

カスタムパラメータ

 プロジェクト内に画像データを埋め込みたいので、エフェクトのカスタムパラメータを用いる。カスタムパラメータは、通常のパラメータと違い、UI、データ構造を自由に出来るが面倒が増える。また、スクリプトから値の取得、設定が出来ない。よって、様々なエフェクトに流用出来るような汎用性の高いものを目指す、かつ、スクリプトから加工できる機能を提供するというのを、個人的な指針としている*1ピクセルデータをそのまま保存するのは容量が嵩むので、以前個人的にプリレンダリング用としてZPngを参考にして作ったフォーマットを流用した。そして、スクリプトから画像ファイルやクリップボードに含まれる画像データを読み込めるようにした。

@image

 画像を取り扱うエフェクト群として、@image, @image_file, @images_in_folderの3種を用途に応じて用意した。@imageは上記のカスタムパラメータを用いて、エフェクト内にピクセルデータを保持するエフェクト。@image_fileは画像ファイルのパスを保持して、その画像を表示するエフェクト。@images_in_folderはフォルダのパスを保持し、そのフォルダ内に含まれる画像を表示するエフェクトである。この3つさえあれば大抵の用途に対応できると現時点では思っている。画像ファイルの読み込み自体はstb_imageに委譲しているので、stb_imageで読み込めるフォーマットは読み込める。

クリップボード

 CLCLPasteboard Viewerを用いてクリップボードの中を眺めると、いろいろな形式が登録されているのが分かる。そこで、登録されている形式、ファイルパス、画像の情報を取得出来るようにし、クリップボードを介したやりとりをやりやすくした。

RIFX

 IllustratorはCC 2014あたりから、コピー時にクリップボードSVGを含められるようになった。すでに@svgを作り、最低限度のSVGを扱えるようにはしてあるが、Illustratorが出力するSVGにもある程度対応したい*2。そこでまず、グラデーション周りに取り掛かることにした。

 カスタムパラメータやグラデーションのようなスクリプトから値を設定出来ないパラメータに対して値を設定する方法としては、プリセットを適用するという手段がよくとられる。プリセット(.ffx)はプロジェクト(.aep)と同様、RIFX*3というファイルフォーマットが用いられている。そこで、RIFXファイルをparseしてjavascriptのオブジェクトに変換、そしてそのオブジェクトを再びバイナリデータに変換する仕組みを作った*4。こうすることで、あらかじめテンプレートとなるオブジェクトを保持しておき、必要に応じてそのオブジェクトに変更を加えバイナリに変換することで、動的にプリセットを生成できる。バイナリをテンプレートとして保持し、それに必要に応じて変更を加えるという手法が大抵使われるが、かなりわずらわしい*5ので割りかし楽になった。

@svg

 グラデーションを弄ることが出来るようになったので、@svgもアップデートした。

Pseudo

 RIFXを弄るツールを整備したので、ついでにPseudo Effect周りも調べることにした。調査過程でパラメータの定義を司るpardチャンクのデータ構造がPF_ParamDefであることが分かったため、Pathパラメータ*6やButtonパラメータ*7も追加できるようになった。現時点だと*8Pseudo Effect Makerを導入する方が賢明。

メッシュワープ

www.youtube.com

 メッシュワープの値をスクリプトから弄られるようにした。rows, columnsの数は頂点の数なので、面の数を表すRows, Columnsパラメータの値に1足す必要がある。

API

 他のスクリプトの機能を呼び出したいなというときが時たまある。例えば、@hook_solidを導入しているならば@hook_solidライクに平面を追加したいな、といったときである。そういうことがやりやすい仕組みを整備した。

@paste_clipboard

www.youtube.com

www.youtube.com

www.youtube.com

www.youtube.com

 今回の更新の集大成として、Alt(Option)+Vで、クリップボードに含まれている形式に応じて色々読み込むスクリプトを作成した。

*1:カスタムパラメータにアクセスするAPIの提供も@scriptの目的の一つではある

*2:具体的には.klass、#id程度のセレクタに対応したスタイルタグを用いた間接的なスタイルの適用及びグラデーション

*3:RIFFのビッグエンディアン

*4:aepの構造はLottieのParsing AEP Filesに幾分かの解析結果が載っており大いに参考にした

*5:RIFXの仕様として、親チャンクが配下の子チャンク群のデータサイズを保持するため、子チャンクのデータを弄ると親チャンクに対して再帰的に変更を加える必要がある。

*6:ファイルパスではなくマスクパスの方

*7:押された時の処理が書けないため完全に無用

*8:そして今後も

色収差再考

キリスト降誕おめでとうございます。

adventar.org

https://atarabi.com/kikaku/plugin/old/#ChromaticAberration

2011年にChromatic AberrationというAEプラグインを公開した。ありがたいことに今もちょくちょくダウンロードされているのだけど、当時の技術的限界もあり、特にパフォーマンス面において難がある。 なので、しばらく前からコンセプトはそのままに一から作り直している。そこで今一度、色収差(風)エフェクトの有り様について考えてみたい*1


色収差風エフェクトは基本的には、色の分解、サンプリング、合成からなる。まずはこれらの処理について大まかに見ていく。

分解

神は「光あれ」と言われた。すると光があった。

色を分解しないことには始まらない。最も単純なのは、R, G, Bの3色に分割することである。そのうち2色を連動させれば、容易に青と黄、マゼンタと緑、赤とシアンの反対色を構成出来る。

サンプリング

神はまた言われた、「水の間におおぞらがあって、水と水とを分けよ」。

分解した色ごとにサンプリング位置を異にすることで色ずれの効果が生じる。倍率色収差ライクにしたいならスケールを変えたり、レンズディストーションさせて、動径方向にずらせばよい。

RowbyteのSeparate RGBはそれから一歩進めて、R, G, Bチャンネルそれぞれに対して、トランスフォーム出来るようにしてある。

https://aescripts.com/separate-rgb/

SapphireのS_TimeWarpRGBは時間軸方向にずらしている。

組み合わせ次第で色々な方向を目指すことが出来る。ただ、実現可能な領域が増えたとしても、"容易に"実現可能な領域が増えるとは限らないので(むしろ減ることもある)、目指したい方向を明確化し、"容易に"実現可能な領域を単調増加させつつ、実現可能な領域も増やしていくのがいいとは思う。

合成

神はまた言われた、「天の下の水は一つ所に集まり、かわいた地が現れよ」。そのようになった。

通常加算で合成するが、後ほど他の可能性もないか検討する。例えば、乗算が絡むものは黒に沈むので難しい。




分解の分解

分解についてもう少し推し進めたい。例えば、単純にR, G, Bに分割するだけだとパキッとしすぎる。

分割数を増やせばスムーズにすることが出来る。

この際、徒に分割したら色調が変わってしまう。なので、元の色調を維持するために、分割した色を足した場合に白になるように調整する必要がある(加算で合成する場合)。

 \displaystyle
(r, g, b) = \sum_{i}(r_{i}, g_{i}, b_{i}) = (1, 1, 1)

最初からそれを考慮するのは面倒なので、とりあえず色を選んでからそれらを足し合わせ、その合計で各色を割れば良い。

 \displaystyle
(r', g', b') = \frac{(r_{i}, g_{i}, b_{i})}{\sum_{i}(r_{i}, g_{i}, b_{i})}

SapphireのS_WarpChromaの場合、White Balanceをチェックすることでその計算を自動でしてくれる。

単純にR, G, Bを分割する場合でも色々な方向性があり得る。

つくね氏によるPixelBenderフィルタ群であるRetinaFiltersに含まれていたChromatic Aberrationや、SapphireのS_WarpChromaでは各色間を補間する形になっている。

一方、Plugin EverythingのQuick Chromatic Aberration 3や、PSOFTのP_ChromaticAberrations(のモードFix Green)では、Gは固定し、R, Bを分割している。

この方式の利点は、効果を強めてもGがブレないため、ディティールが潰れないところだろうか。

ちなみに、P_ChromaticAberrationsは2023年に更新されて、色選択のモードが増えて便利になった。

サンプリングと分解

上のような、サンプリング数を調整できる類のプラグインは、いい感じに分割出来るようにトランスフォームに一定の制約をかけている。

例えば、S_WarpChromaはFromとToとを指定する形にしており、その間を容易に補間出来るようにしてあり、

Quick Chromatic Aberration 3は、Gは固定で、パラメータを弄ると、R, Bがそれぞれ連動して逆方向に変化するようになっており、これまた補間しやすくしてある(位置をずらすパラメータであるPositionが、左上、右下への移動に固定されているのが解せないが)。

一方、Separate RGBのようにR, G, Bそれぞれを自由にトランスフォーム出来るようにしてあると、いい感じに割るのが難しくなる。

これらの違いは当然想定している表現の方向性による。

合成の可能性

加算以外の可能性を考える。色調を保つために重要なのは、分割した色を合成していって白を作れるかである。加算の場合は単純に足していけばいいだけなので簡単である。他に容易なのに比較明がある。比較明だと、分割した色の中に値が1となるようなものが含まれていれば良い。つまり、

 \displaystyle
(r, g, b) = \max_{i}(r_{i}, g_{i}, b_{i}) = (1, 1, 1)

を満たせば良い。

加算の場合、分割数を増やすと、一つ一つの色の重みが必然的に小さくなる。

比較明の場合、そういった縛りはなくなるため、望み通りの色をそのままどぎつく出せる。

表現の方向性

どういった表現を想定して設計するかで、パラメータの設定の仕方、とんがり方は当然異なる。グリッチ表現を想定するのであれば、ちょっとしたパラメータの変化で大きく表情を変えるようなものが望まれるだろう。

自分の場合、周辺にちょっとした味をつける、周辺の解像度を落とすことで視線誘導させる、といった使い方にウェイトを置いている。

例えば、EFX Chromatic Aberrationにおいて、Scalingをlinear, square, cubicと切り替えられるが、

https://aescripts.com/efx-chromatic-aberration/

このように掛かり具合を変えられるようにすることで、周辺部だけに掛かるようにする、見せたい中心部の解像度は保つ、といったことが可能となる。

また、ズレ量に応じて、放射ブラーを掛けられるようにすれば視線誘導の効果が際立つ。

その先に

こうして天と地とその万象が完成した。

今回は色収差系のプラグインを色々取り上げたが、みなさんも同種のプラグインを比較しその差異を比べることで、その背景にある設計思想を想像してみてはいかがだろうか。

*1:色収差の光学な話については、ComperK氏のVFXと色収差 - コンポジゴクという記事に詳しい

After Effectsのスクリプトの可能性を広げる。 その5

github.com

github.com

ちょっとした修正といくつかのスクリプトの追加と更新。

修正

エフェクトマスクのパラメータのバグ

エフェクトマスク周りのパラメータの扱いに難があったのを修正した。エフェクト麾下のパラメータは、0番目のパラメータがそのエフェクトに対する入力を扱うパラメータとして予約されているので、他のパラメータと扱いが少し異なる。そこらへんの処理が少々雑だった。

UI.showContextMenu()のバグ(Windows下)

上のようなコンテクストメニューを表示させるAPIで、変数のライフタイムの管理が雑な箇所があったので修正。

Fuzzy Searchのバグ

計算コスト低減のためにキャッシュ機能を実装しているが、そこにバグがあったので修正。

スクリプト

@script_panel

使用頻度の小さいScriptUI系のスクリプトをScriptUI Panelsフォルダに入れるのが煩わしい時に、あるフォルダに入れておいて、ドロップダウンリストから呼び出せるようにする。挙動(特にUI周りの)は当然、対象のスクリプト次第。

@proxy_maker

www.youtube.com

重いコンポをプリレンダしてプロキシに設定するというのはままあるが、それをレイヤー単位でしたかったので作成。

@effect_launcher

ある文字列を入力すれば、即座にエフェクトが適用されるショートカット機能を追加した。スクリプト、プリセットもショートカットの対象に出来る。

スクリプト、プリセットも検索の対象に含められるようにした。検索窓に\\を入力すれば設定画面に入れる。

ドキュメント

APIばかりの説明で、スクリプトの説明がなかったので、簡易的だが説明を追加した。

Fuzzy Searchとdebounce

@effect_launcherや@effect_searchなどでは、キー入力ごとにFuzzy Searchを発火させると、処理が追いつかず固まるので、debouneで処理を間引いているが、debounce timeはちょっと安全側に寄せて200msに設定していた。しかしこの設定だと特にショートカット機能を用いた際の遅延が気になったので、100msに変更した。また、使用者が自分で変更出来るように、変数に切り出してスクリプトの上の方に置いた。

After Effectsのスクリプトの可能性を広げる。 その4

github.com

SVGに関する機能と、トーンカーブなどのカスタムデータ周りの機能を追加した。

SVG

www.youtube.com

SVGを読み込む際は、例えばIllustratorでAIファイルにして読み込むなど手間が掛かる(シェイプレイヤーにする場合は読み込んでから更に変換)。そこで、AE、スクリプトの機能を活用し、ドラッグ&ドロップで、容易にシェイプレイヤーとして読み込めるようにした(静的なベクター素材として用いたい場合は、AIファイルにした方が軽いので無難(特に複雑な場合))。基本的にSVG 1.1を対象にしているが、useタグなど未対応のものがあるし、グラデーション周りはスクリプトから設定することが出来ないのでそもそも対応出来ない。

AEと入出力

AEで、ある拡張子のファイルを読み込ませたい等、入出力周りの機能を追加する方法は主に3つある。1つ目は、Premiere Pro SDKを用いて開発するMediaCore Importer/Exporter Plugin。Premiere Pro、After Effects、Media Encoderをサポートするので汎用性が高い。2つ目は、AEIO。AEGPプラグインの中でもメディアファイルの入出力周りを扱うものを便宜上そう名付けてあるようだ。AE専用になるので、明確な意図、意思がない限りにおいては、MediaCoreを選択する方が無難。最後は、AEGPの中のFile Import Manager Suiteを使う方法である。AEIOがピクセルデータを扱うの対し、FIMSuiteは、

they are for importing projects which are best represented as After Effects compositions.

とあるように、構造や指示が書かれてあるテキスト、バイナリデータを読み取り、それに基づいてコンポジションなどを構築することを想定している。しかし、C++でこういった操作を書くのがそもそも面倒であるし、別にスクリプトでいいんじゃとなってなかなかいい用途が見つからない。ただ、SVGの読み込みには使えるなと以前から思っており、@scriptのRegister.importFlavor()で、スクリプト側で処理を書けるようにしてあるので、今回はそれを用いた。

XML

ExtendScriptECMAScript for XML(E4X)を実装しているので、XMLを気軽に扱える。一方、SVGも中身はXMLであるのでこれが使える。ただE4Xは、独自の記法があったり、名前空間を厳密に扱わないと要素を取得できないなど色々と面倒。SVGは大抵http://www.w3.org/2000/svgをデフォルト名前空間に設定してあるので、 default xml namespace = "http://www.w3.org/2000/svg"; という独自記法か、それの代替となるsetDefaultXMLNamespace関数を使うかしてデフォルト名前空間を設定する必要がある。ただ、この命令のスコープが関数ブロックレベルで、毎回毎回指定する必要がある。そこで単純にxmlns="http://www.w3.org/2000/svg"を削除することで、名前空間周りの面倒を取り除いた。

カスタムバリュー

www.youtube.com

www.youtube.com

カスタムデータの中でも、ただのデータの塊みたいなシンプルな構造のものは、どんな構造かさえ分かれば外部から設定することができる。今回は、トーンカーブとコロラマのカスタムデータをスクリプトから取得、設定出来るようにした。


同梱物

新たに追加したもの。

@svg

SVG周りの機能はStartupスクリプトの@svg.jsxに実装されているので、プラグインだけでなくこれも導入する必要がある。

After Effectsのスクリプトの可能性を広げる。 その3

github.com

いくつかの機能追加とバグフィクスを行った。マウス周りのフックは他のフックに比べ応用範囲も狭いだろうから(可能性があるのはダブルミドルクリックくらい?)熟成させていたのだが、花雪さんからバグ報告をいただき、その修正にともない熟成も止めることにした。

[bug]Add to Adobe Media Encoder Queue... · Issue #1 · atarabi/at_script · GitHub

バグの内容は、!@script_initializer.jsxを導入した状態でAdd to Adobe Media Encoder Queue...をクリックするとエラーが発生します。ということで、最初は信じられなかった。というのも、!@script_initializer.jsxはバイナリ形式にしてあるものの中身はいたって単純なのでそもそもバグを出す余地があるのかというのと、AfterFX.exeでエラーが出ているのだが、何で今更エラーが出るのかという話でもある(!@script_initializer.jsx自体Startup時に実行されるスクリプトであるし)。

詳しく調べてみると、AMEが立ち上がる際にdynamiclinkmanager経由でAfterFXを新たに起動しており、その条件下においてStartupのスクリプト内でapp.executeCommandなど(他にもあったがあまり調べられてない)のメソッドを実行するとクラッシュするらしかった。dynamiclinkmanagerには思い当たる節があって、ある時からWarp Stabilizerエフェクトがきちんと動かなくなり、それもdynamiclinkmanagerが原因の一端であることまでは分かっていたが、あまり使用することもなかったので放置していた。案の定これも同じ原因で落ちてるらしかった。理由が分かれば対処も早い。

Startup系のスクリプトには早期リターンしてもらう必要があるので、!@script_initializer.jsx内でdynamiclinkmanagerから実行されてるかの判別を行うAtarabi.isDynamicLink()を定義し、それを呼び出してもらう形にした。

(() => {

    if (Atarabi.isDynamicLink()) {
        return;
    }

    // mainの処理
})();

マウス周りのフックは上でも書いたが全然使い道が思いつかない。ただ、作っておけば賢明な人がなにか使い道を思いついてくれるだろう。

// ダブルミドルクリックをするとコンポをつくる。
const uuid = Atarabi.mouse.hook({ button: 'Middle', count: 2 }, ctx => {
    app.project.items.addComp('Comp', 1000, 1000, 1, 100, 30);
    return true; // trueを返した場合、デフォルトの処理が呼ばれなくなる。
});

同梱物

新たに追加したもの。

@effects_in_use

プロジェクト内で使われているエフェクトをスキャンするスクリプト。同種のものは既にあると思うが、自分が使いやすいと思うものを作った。

@swatch

カラースウォッチの試作。スクリプトのパネル上でミドルクリックをすると、色パラメータを選択もしくは選択したエフェクト内に初期値から変更済みの色パラメータがあればその色を、それ以外の場合かつ平面レイヤーを選択していればその平面の色を取得する。削除したい場合は、当該色の上でダブルクリックをする。

After Effectsのスクリプトの可能性を広げる。 その2

github.com

公開したことでやる気もすこし出てきたので、いくつか更新した。

自分の思想として"ソフトがこちらに合わせろ"というのがあり、既存コマンドに対するフックを容易にするというのもそれに向けての一つの手であったが、更に押し進めてキー入力をフック出来るようにした。

キー入力のフックを上手く活用した一番有名な例はFX Consoleであろうが、こういったランチャーの嚆矢はQuick Menuだと思われる。

AEではScriptsフォルダにあるスクリプトに対してショートカットを割り当てることが可能なので、それを活用している。この方法は言われてみればという感じで、当時甚く感心した。

最大20個までショートカットを設定出来るようなので、やろうと思えば色々出来る。

難点は、管理が面倒だというのと、スクリプトがその都度実行されるため、特に重い処理が含まれる場合に、毎回我慢するか、計算結果をグローバル変数などに逃がしてキャッシュしておく必要がある。Quicke Menuの場合、appオブジェクトに何か生やしているようであった。

キー入力のフックは、このような本質的でない部分による煩わしさから逃れられることが利点であると言える。ただどういった状況下でキー入力がなされたかという情報がないため、例えばレイヤー名変更のために文字入力しているという状況下でも容赦なくイベントは発火する。したがって修飾キーの指定は必須だと思われる。

使い方は至って単純。

(() => {
    Atarabi.keyboard.hook({ code: ']', altKey: true }, ctx => {
        const comp = Atarabi.comp.getMostRecentlyUsedComp();
        if (!comp) {
            return false; // falseを返した場合、(存在すれば)AEのデフォルトの操作が実行される。
        }
        const layers = comp.selectedLayers;
        if (!layers.length) {
            return false;
        }
        try {
            app.beginUndoGroup(']');
            for (const layer of layers) {
                layer.outPoint = comp.time;
            }
        } catch (e) {
            // pass
        } finally {
            app.endUndoGroup();
        }
        return true; // trueを返した場合、AEのデフォルトの操作は実行されない。
    });
})();

Alt+] コマンドは、アウトポイントが現在フレームではなくその次のフレームに設定される。それが煩わしいという人も時たま見るので、上のスクリプトでは現在フレームをそのままアウトポイントに設定するようにしている。

AEのコマンドには、コマンドIDというものが設定されていてそれ経由で実行されるコマンドと、そうでないコマンドとがあり、]Alt+] は後者に属する。なので、コマンドのフックで何とかなる前者だけでなく、キー入力のフックもあれば後者も網羅出来て完璧、とはならず、先程書いた通り、修飾キーのない ] をフックするのは難がある。残念。


同梱物

新たに追加したもの。

Startup

!@script_UI

以前は、スタートアップ時にUI周りのコードを使用しないだろうと思っていたが、割りと使いうると分かったので、リネームして頭に ! をつけることで読み込み順を早めている。

@effect_launcer

www.youtube.com 簡易的なエフェクトランチャー。Ctrl+SpsaceでエフェクトをFuzzy Search出来る。 !@script_UIを用いているので、@script_UIのままの人は頭に!をつけてください。

@hook_]

上では、Alt+]の例を紹介しているが、使用例として一応]も用意してある。

@toggle_mfr

(() => {
    const SECTION = 'Concurrent Frame Rendering';
    const KEY = 'Enable Concurrent Frame Renders';
    Atarabi.keyboard.hook({ code: 'M', altKey: true }, ctx => {
        const now = app.preferences.getPrefAsBool(SECTION, KEY);
        app.preferences.savePrefAsBool(SECTION, KEY, !now);
        writeLn(`MFR: ${!now}`);
        return true;
    });
})();

短いので全部掲載するが、Alt+MでMulti-Frame Renderingのオンオフを切り替える。

@save_to_desktop

(() => {
    Atarabi.keyboard.hook({ code: 'S', altKey: true }, ctx => {
        const comp = Atarabi.comp.getMostRecentlyUsedComp();
        if (comp) {
            const file = new File(`${Folder.desktop.absoluteURI}/${comp.name.replace(/\//g, '-')}_${Date.now()}.png`);
            Atarabi.comp.saveFrameToPng(comp, file);
        }
        return true;
    });
})();

Alt+Sで現在表示されているコンポの画をデスクトップにpngで保存する。


余談。

エフェクトのFuzzy Search周りでは、EditTextのonChangingのコールバック関数にdebounce処理をほどこしている。すべてのイベント発火に対して処理を行うとかなりもたつくからである。

debounce処理の実装にはsetTimeoutとclearTimeoutが使われるが、AEのスクリプトにはないので、app.scheduleTask()、app.cancelTask()を代用することになるが、残念ながら実行したい処理を文字列で指定する必要があるので、グローバルに関数を晒す必要があり非常に使いにくい。

幸いにもCC2015から、app.setTimeout()、app.cancelTimeout()が隠れ機能として追加され(saveFrameToPngと同じ立ち位置?)、これは普通に関数を指定できる。ただ自分が調べた印象だと、app.cancelTimeout()は機能してないように思われる。