幸い、公式でSDKが提供されていたので、それを基に作ってみようとしたのですが……。
なんとサンプルプログラムが一つしかありません。
ことらは、ブロックごとに画素を操作する方法を紹介していました。しかし、私はビットマップ全体を操作する、定番のやり方をしたかったのですが、その方法がどこにも載っていません。しかも<関数名でGoogle検索しても、わずか3件しかヒットせず。
ドキュメントに関数の使い方だけは書いてありますが、使用例がないと、どうにも使い方がわかりません。
僅かに得られた参考文献から導き出した、このHello world完成までの行程を紹介し、少しでも多くのプラグイン開発者の道しるべになればと思い執筆いたしました。
・プロジェクトの作成
・骨組みの作成
・5つの処理を作る
・プラグインの終了処理
・フィルタの終了処理
・フィルタの計算処理
開発環境は以下のとおり
・Visual Studio 2019
・Windows10
まず、VisualStudioを起動し、"C++/Dynamic Link Libray (DLL)"でプロジェクトを作成します。
プロジェクト名はHelloCSPluginとしていますがお好みで結構です。
次に、CLIPSTUDIOのSDKを導入します。とはいえ、これはヘッダーだけで構成されているのでコピーだけで終わります。以下のリンクからSDKをダウンロードします。
zipファイルのFilterPlugIn20210827.zip\FilterPlugIn20210827\FilterPlugIn\TriglavPlugInSDK フォルダをプロジェクトのフォルダにコピーします。
次に、プロジェクトの設定を行います。プロジェクトのプロパティから、全般、出力ファイルの拡張子を.dll→.cpmにします。
そして、C/C++の全てのオプションより、強制インクルードファイルにpch.hを加えます。
dllmain.cppが自動で作成されているので、それを開き、画像の20行目以降に書かれたプログラムを記入します(23行目はコメントなので不要)。
"TriglavPlugInSDK/TriglavPlugInSDK.h"は先ほど追加したSDKです。
switch内部で、5つの処理に分かれていることが確認できるでしょうか。実際、クリスタのプラグインは、この5つの処理しか持ちません。そして、ここで紹介するプラグインは、登録と画像処理だけなので、沢山書くのは2つの処理に限られてしまいます。こうしてみると、プラグイン製作はさほど難しくないことがわかるのではないでしょうか。
なお5つの処理は以下のとおりです。
kTriglavPlugInSelectorModuleTerminateプラグインの初期化を
kTriglavPlugInSelectorModuleTerminate プラグインの終了処理
kTriglavPlugInSelectorFilterInitialize フィルタの初期化
kTriglavPlugInSelectorFilterTerminate フィルタの終了処理
kTriglavPlugInSelectorFilterRun フィルタの計算処理
5つの処理があるというならば、5つの関数を作ればよいということです。すなわち、以下の画像のように関数を作っておきます。switch内部はそれぞれの処理を担当する関数を呼ぶように変更します。そして、戻り値をresultポインタの示す番地に書き込みます。
各関数の引数はdataとpluginServerの2つがあれば十分です。それぞれプラグイン独自のデータ (今回は不使用) とクリスタとの通信用です。
《プラグインの初期化 ModuleTerminate》
バージョンチェックの後、モジュールIDを設定します。このプラグインが必要とするバージョンがクリスタより低い場合に起動できると判断します (L40)。そして、モジュールIDを設定します (L47)。このIDは複数のプラグインで重複してはいけないので、注意してください。
《プラグインの終了処理 ModuleTerminate》
ModuleTerminateは今回は何も必要がありません。成功を返却するだけにします。
《フィルタの終了処理 FilterTerminate 》
こちらも何も必要がありません。成功を返却するだけにします。
《フィルタの初期化 FilterInitialize 》
フィルターの設定をします。
L71でresource.hを取り込んでいます。これは、リソースという、メニューに表示する文字列を書き込んだファイルです。その文字列の識別IDを管理しているのがresource.hです。
これがL87, 88でID_FilterCategoryName、ID_FilterNameという紫の文字が見えますが、これがそうです。文字列の指定はリソースを介さなければならない仕様なので、ソースコードに直接埋め込めないのです。
L104から、ターゲットのレイヤーを指定します。カラーモードのラスターだけを対象とします。これは、配列で指定するので、L72のvectorが必要となります。配列の要素数も.sizeで取得できるので便利です。
リソースは以下のように追加できます。プロジェクトを右クリックで新しい項目を選ぶと画像のようになります。そこでrcファイルを選んで追加してください。
文字列テーブルを新規作成します。
L87, 88で書き込んでおいた、ID_FilterCategoryName、ID_FilterNameのIDの文字列を作り、好きな文字列を入れます。
実際には、このような文字列になりますが、お好みで変えることができます。
《フィルタの初期化 計算処理 》
フィルタの本体です。
for分を多用するので、あらかじめrepマクロを定義しておきましょう。こうすると、for(long i;i<...;i++)と、変数を3度書きしなくて済みます。
最初の方は、pluginServerから必要なオブジェクトを引き出しているだけです。
今回はビットマップを扱うので、
pBitmapService
sourceOffscreenObject
destinationOffscreenObject
が入出力で重要になります。
pBitmapService->createProcでsourceBitmapObjectという、画像を保持するバッファを確保します。
これに、L162のgetBitmapProcで画面上の表示内容をコピーします。これで処理の準備は整いました。
そして、RGBのインデックスを取得しておきます。
通常、ピクセルは(R,G,B,A)のように保存されるはずですが、変化する可能性もあるのでしょう。
次のfor分が処理の本体で、最後になります。これは、while(1)に近く、restart変数を最初に定義しているにすぎません。
restartは最初の初期化処理を行うかの変数になります。L177を最初に実行します。今回は特にないのですが、ダイアログのパラメータを設定しなおす場合に使います。
L192の二重ループこそがコアな部分です。座標 (x, y) の位置のピクセルを取得し、そのピクセルをNOTで反転しているだけです。
L211では、処理の終わったビットマップをレイヤーの描画データである、destinationOffscreenObjectに戻しています(sourceOffscreenObjectではありません!)。
以上になります。最終的に、プロジェクトは以下のようになります。
では実行してみましょう。リソースで設定した文字列がメニューに設定されていると思います。これをクリックしてみましょう。
なお、今回は選択範囲を矩形しか考慮していないため、投げ縄で選択すると矩形で適用されてしまいます。
実行環境がVisualstudioの外、すなわちClipstudioの配下です。そのため、通常のアプリケーションのようにはデバッグできません。
そこで、デバッグ用の処理を追加し、メッセージを送信します。そして、それを外部のアプリケーションで読み取ることにします。
まず、以下のファイルをプロジェクトに追加します。
中身は気にしなくて構いません。
DBG.hをインクルードします。
デバッグメッセージを出したい場所にLOG(...)を設置します。書式はprintfです。__func__はその実行されている関数名に置き換わります。
ここでは、ModuleInitializeの登録成功の直後に置いてみました。
実行してみましょう。メッセージを受け取るには、DebugViewを利用します。Clipstudioの実行直後に、"SUCCESS: ModuleInitialize"が表示されているのがわかるでしょうか。なぜか2回実行される仕様のようですが、デバッグメッセージは正しく出ています。結果、登録に成功していることがわかります。
SDK付属のサンプルですが、
構造体->
ではなく
(*構造体).
と書いていたり、
resource.hを取り込まず、その値をソース側で再度書く、
resource.rcを更新するとafxres.hをインクルードしてしまったりと、
コードの書き方に少々奇妙さを感じました。
また、世の中のプラグイン開発者は、なぜこの少ない情報から易々と開発できるのかがいまだにわかりません。CG制作会社だけは特別に技術供与を受けているのでしょうか…。
貴重な参考文献です。
githubからダウンロードできます。ご自由にお使いください。こちらを改変していただき、プラグイン製作にご活用ください。