プログラム講座 上級編2
- Photoshopプラグインフィルタの作成 -
 上級編2です。
 今回はグラフィック関係のプログラムを作成した事がある人ならば作ってみたいと思った事があるかもしれない「フォトショップのプラグインフィルタ」の作成について解説します。まだ、全部把握できていませんので、不明な部分もあります。特に日本ではDevelopers Journal Japanくらいしか日本語の解説がないという状況です。多少の間違いや不明点があっても、笑って許してやってください。私の方が詳しく知りたいくらいです(^^b
◆Adobe Photoshopについて
 グラフィック関係やDTP関係に手を付けていない方はPhotoshop(フォトショップ)を知らないかもしれません。それでもPhotoshopという名前は聞いたことがあると思います。PhotoshopはMacintoshの寿命を大幅に延ばしたと言ってもいいでしょう。
 Photoshopについて簡単に説明しておきます。Photoshopはお絵かきソフトではなく「フォトレタッチソフト」と呼ばれる写真加工用に使われます(絵を描くのに使っている人も多々います)。加工の際に多く使用されるのが「フィルタ」です。エンボス加工やモザイク、ぼかしやシャープ、逆光などなど非常に多くのフィルタがあります。これらのほとんどが「プラグイン」という仕組みによって実現されています。このプラグインの数はMacintoshが最も多く、また他のソフトでも使用する事ができる優れものです。
 ちなみにPhotoshopはMacintosh以外にWindows 3.1, 95, NT/SGI/SUNなどにも移植されています。プラグインは機種毎にインターフェースが異なります。当然、ここで解説するのはMacintosh用です。Future BASICでPhotoshopのプラグインができてしまうのですから、こりゃラッキーと思ってよいのではないでしょうか。
◆プラグイン情報の入手先
 NiftyServe上であればGO ADOBEJでPhotoshopのプラグイン作成キットを入手できます。インターネット上でもアドビシステムズのページにあると思いますが確認していません。
 プラグイン自体の入手はプラグインセンターやNiftyServe上(FGRAPHICのライブラリが一番整備されています)で入手する事ができます。プラグイン作成の場合は、NiftyServe上のFMACPROで質問する事もできます。が、あまりプラグインの質問は見かけません(^^b
 日本のMacintosh関係のホームページを見てもPhotoshopのプラグインの作成についてかかれた所は現在の所不明です。多分1、2カ所はあると予想されるのですが・・・。
◆Photoshopについての知識
 今回は今までのソフトとは異なりPhotoshopの知識が必要です。Photoshopの本はたくさん出回っているので、それを読むのも良いでしょう。とりあえず、知っておかなければならない事柄について説明しておきます。要するにプラグインに関係するものについて説明するという事です(^^b
■プラグイン
 プラグインはPhotoshopの機能を拡張するものです。プラグインはフィルタ処理だけを行うものと思っている人が多いと思いますが、実際には以下の種類があります。
・入力モジュール
 スキャナなどから画像を取り込んだり、特定の画像形式を読み込んだりするタイプのものです。このタイプのプラグインはファイルメニューの「入力用プラグ」の階層化メニューに表示されます。
・出力モジュール
 入力とは逆に特定のファイル形式などを書き出すタイプのものです。このタイプのプラグインはファイルメニューの「出力用プラグ」の階層化メニューに表示されます。
・フィルタモジュール
 もっともたくさん作成されているタイプのものです。指定画像を加工するものでフィルタメニューに表示されます(階層化されて表示されます)。
・ファイル形式モジュール
 特定の画像形式を読み込んだり書き出したりするタイプのものです。これらはファイルオープン、保存ダイアログのポップアップメニュー(ファイル形式:)の一覧に表示されます。
・拡張モジュール
 DSPプラグインやマルチCPUなど特殊な機能をサポートするタイプのものです。アドビシステムズに頼まないと資料等は出してくれないみたいです。
■画像モード
 Photoshopには様々な画像モードが用意されています。どの画像モードでも描画できる所は結構優れものでしょう(モードによって制限はありますが)。このモードですが、以下のような画像モードがあります。
白黒モード
グレースケール
インデックスカラー
RGBカラー
CMYKカラー
HSLカラー
HSBカラー
マルチチャンネル
ダブルトーン(デュオトーン)
Labカラー
 プラグインを作成する場合は、この画像モードが大きく関係してきます。プラグイン側で指定の画像モードでなければ選択不能(メニュー段階で)にする事ができます。
 プログラムでは以下のように定義してあります。
| 番号 | 定数名 | 画像モード | 
| 0 | _PSModeBitmap | 白黒モード | 
| 1 | _PSModeGrayScale | グレースケール | 
| 2 | _PSModeIndexedColor | インデックスカラー | 
| 3 | _PSModeRGBColor | RGBカラー | 
| 4 | _PSModeCMYKColor | CMYKカラー | 
| 5 | _PSModeHSLColor | HSLカラー | 
| 6 | _PSModeHSBColor | HSBカラー | 
| 7 | _PSModeMultichannel | マルチチャンネル | 
| 8 | _PSModeDuotone | ダブルトーン(デュオトーン) | 
| 9 | _PSModeLabColor | Labカラー | 
■チャンネル
 フォトショップの重要なポイントが、この「チャンネル」です。チャンネルというのは表示に必要な色のプレーン数の事です。こう書いてしまうと、何だ?という事になってしまいますので具体的に説明します。
 フルカラー画像を表示するのに最低必要なチャンネルはRGB各8ビットです。このR(赤)成分で1チャンネル、G(緑)成分で1チャンネル、B(青)成分で1チャンネル、合計3チャンネル必要です。CMYK画像であれば、C(シアン)成分、M(マゼンダ)成分、Y(黄)成分、K(黒)成分、合計4チャンネルあります。グレースケール画像であれば、グレー成分しかありませんので1チャンネルになります。
 これに加えてPhotoshopではαチャンネルという透明度情報を持ったチャンネルをいくつも持つことができます。RGB画像に1つαチャンネル情報があるとすれば、合計4チャンネルという事になります。
 Photoshopを起動してチャンネルを見た方が早いでしょう。
◆フォトショップとプラグインのやりとり
 とりあえず最低限の説明はしましたが、わからなければ本屋にでも言って調べるか知人に聞いた方がよいでしょう。
 さて、プラグインはPhotoshopが起動すると自動的に読み込まれてメニュー項目に追加されます。そして、プラグイン側で有効な画像モードである時にだけ、フィルタを選択することができます。今回は単純なフィルタを作成してみます。楽なフィルタと言うことで乱数で適当な色の点を表示する「ランダムノイズ」というものです。
 Photoshopからプラグインが呼び出されると以下のような感じでやりとりが行われます。
(1)プラグインのワークエリアの初期化(起動時かな?)
(2)パラメータ入力があるか? あれば入力
(3)フィルタの開始処理
(4)フィルタの実行
(5)時間がかかる場合は進行状況をPhotoshopに知らせる
(6)フィルタの実行が終了したか、キャンセルボタンが押されたら、終了処理をする
(7)アバウト画面を表示する要求がきたらアバウト画面の表示をする
 だいたい、こんな感じになっていて、この要求に対応した関数を作成していけばいいという事になります。プログラムリストでは、これらは以下のようにしてあります。
| 呼び出し番号 | 定数名 | 内容 | 
| 0 | _PSFAbout | フィルタのアバウト画面の表示 | 
| 1 | _PSFGetParameters | パラメーターの入力を行う場合 | 
| 2 | _PSFPrepare | フィルタの初期設定要求 | 
| 3 | _PSFStart | フィルタ処理の開始 | 
| 4 | _PSFContinue | フィルタ処理の実行 | 
| 5 | _PSFFinish | フィルタ処理の終了処理 | 
◆インラインアセンブラ
 今回ばかりは、さすがに全部BASICでやろうとすると「中断処理をしない」「プログレスバー(進行ゲージ)を表示しない」という部分を無視しないと実現できません。という事で、この2つの部分は付属のCD-ROMに用意されているプラグインのサンプルを流用させてもらいましょう。
◆PiMIリソースの作成
 プログラムを作成する前にリソースエディタで先にリソースを作成しておく必要があります。どんなリソースが必要かというと、Photoshopでメニューバーに表示される文字列などの情報が入ったPiMIリソースです。本体のプログラムとインフォーメーションブロックは8BFMリソースに入ります(フィルタでないプラグインは別のリソース名になります)。
 リソースエディタを起動するとアバウト画面が表示されるのでクリックします。すると以下のような画面になるので「NEW」ボタンを押します。

作成するリソース名を入力します。

Resourceメニューから「Create New Resource」を選択します。

リソースタイプを指定するダイアログが表示されます。今回は既存のリソースタイプはありませんので「PiMI」と入力します。

すると以下のような16進入力状態になりますので「0004」と入力します(プラグインバージョン)。

ウィンドウを閉じてResourceメニューから「Get Resource Info」を選択します。

リソースIDを1000、リソース名を「塗りつぶしシリーズ」にします。このリソース名がフィルタメニューに表示される項目になります。

 あとは、アバウト用のALRTリソースを作成します。アバウト用のリソース作成については、別の講座を参照してください。
◆プラグイン情報ブロックとグローバルファイル
 今まではグローバル変数が、ほとんどなかったため1つのプログラム内に記述していました。Future BASICは、大きなプログラムも作成できるように、またプログラムの使い回しができるように、プログラムを種類別に分ける事ができます(詳しい解説は「ハンドブック」の25ページを参照してください。)。
 今回のプログラムは「グローバルファイル」(psHeader.glbl)、「メインファイル」(psNoise.Main)の2つのファイルで構成されています。
 グローバルファイルには、定数とフォトショップとプラグインの間でやりとりをするための「情報ブロック」が定義されています。情報ブロックを説明すると長くなるのでプログラムを参照してください(一応次回説明する予定です)。中にはわからないものもありますが、だいたいコメントしてあるもので用に足りると思います。情報ブロックは「レコード」形式になっています。レコード形式は複数の異なるデータを一括して扱う事ができます。C言語でいう「構造体」と同じです。
 定数はフォトショップの画像モードやエラーコードなどを定義しています。
◆コンパイラに渡すパラメーター
 先ほど作成したリソースを使用するのでRESOURCES命令を使用します。
RESOURCES "psNoise.res", "8BFM8BIM", "8BFM", _resID, "ランダムノイズ"
 "psNoise.res"が読み込むリソースファイル名です。次が作成するプラグインの「ファイルタイプ(8BFM)」と「クリエーター(8BIM)」です(フォトショップのクリエーターは8BIMです)。"8BFM"は作成するリソースタイプです。フォトショップが読み込んで使用する、実行するプログラムはこの8BFMにかかれていなければなりません(プラグインの種類が異なれば、また別のリソースになります)。
_resIDはリソース番号です(1000にしてあります)。次がリソース名で、これがフィルタメニューの階層化メニューの時に表示される実際のフィルタ名になります。
COMPILE 0, _AppendRes_StrResource_DIMmedVarsOnly
 _AppendResはすでに存在するリソースに、作成するリソースを追加します。_StrResourceは文字列があればSTR#リソースに格納します(今回は使用してません)。_DIMmedVarsOnlyはDIM宣言されていない変数があればエラーを促します。コールバックルーチンや今回のようなENTERPROC命令を使用する場合は、必ず指定しておきましょう。そうしないと、変数の値が不定になりフォトショップごと止まってしまいます。
◆フォトショップから渡されるデータ
 実際にフィルタ処理をする部分はFN OneLineProc% (FilterInfoPtr&, GlobalsHndl&)関数です。独自のフィルタを作成するのであれば、この関数を書き換えればよいでしょう。もう1つのプラグインの輝度を半分にするものは、この関数だけを書き換えています。
 フィルタ処理を行うからには、フォトショップから画像データが渡されるわけですが、100MBある画像をいきなり渡すような仕組みにはなっていません。基本的にフォトショップから渡されるデータは横1ラインのピクセルデータです。画像モードがRGBで横4ピクセルであれば以下のようなデータが渡されます。
RGBRGBRGBRGB
 フォトショップにはRGBモードだけでなくCMYKモードなどもあります。同様にCMYKの場合は以下のようになります。
CMYKCMYKCMYKCMYK
 さらにフォトショップの場合は「αチャンネル」という透過マスクがあります。RGBモードでαチャンネルが1つの場合は以下のようになります。
RGBαRGBαRGBαRGBα
 つまりチャンネルパレットに表示されているプレーン分だけ渡される事になります(αチャンネルがない場合、RGBは3プレーン、グレースケールは1プレーン、CMYKは4プレーンになります。前に説明したチャンネルを参考にしてください)。
 この1ピクセルあたりのプレーン数(バイト数)はA% = FilterInfoPtr&.psPlanes%とするとA%にプレーン数が入ります。隣のピクセルは、このプレーン数分だけずれているという事になります。
 フィルタ処理を行う横の長さはFilterInfoPtr&.psOutRect.Right% - FilterInfoPtr&.psOutRect.Left%で求める事ができます。横の長さ分だけフィルタ処理を繰り返せばOKです。
 肝心のフォトショップから渡される画像データはどこに格納されているか求めなければなりません。フォトショップから渡される画像データはFilterInfoPtr&.psInData&にポインタとして格納されています。つまり
inputPixMap& = FilterInfoPtr&.psInData&
 とinputPixMap&にポインタを読み出せばR% = PEEK(inputPixMap&)で最初のプレーンの値が読み出せます(1バイト読み込み)。
 逆にフィルタ処理した画像データを出力するためのポインタはFilterInfoPtr&.psOutData&に入っています。つまり
  outputPixMap& = FilterInfoPtr&.psOutData&
 とoutputPixMap&に書き出す先のポインタを読み出せば、POKE outputPixMap&, 250のように書き出す事ができます。基本的にフィルタ処理はピクセルを読み出す、加工する、書き出すの繰り返しです。これを1ラインづつ繰り返し縦の長さだけ処理すればよいのです。
◆プログレスバー
 プログレスバーというのは、フィルタの処理の進行状況を示します。フィルタ処理に時間がかかる場合フォトショップが自動的に進行状況を表すウィンドウとバーを表示してくれます。この時に、どれくらい進行したのかをフォトショップに渡す必要があります。グローバル変数を1つ用意して1ライン処理が終了する毎に1カウントしてもよいでしょう。ここでは、Y座標の差分=進行度になっています。
 プログレスバーをアップデート(書き換える)部分はCD-ROMのサンプルそのままです。マシン語なので、まあ仕方がないでしょう。ありがたく流用させてもらいましょう。
◆終わりに
 次回も、またPhotoshopのプラグインの続きです。今回は単純に描画するだけのものでしたが通常は元画像を加工するものがフィルタ処理ですので、次回は元画像を読み込んで加工するという、基本部分について説明します。
 今回のプログラムはFuture BASIC 1.0.2でもコンパイルする事ができます。ただ、何回もコンパイルしていると、うまくプラグインが作成できない場合があります。そのような場合は、リソースエディタやPhotoshop、Future BASICを終了させてください。その後に、再度Future BASICを起動しコンパイルしてください。
 さすがに、プラグインの説明は長くなります・・・。ここまで読んでくれてご苦労様でした。でも、次回も続きがあります・・・
◆今回のプログラムリスト
※psHeader.glbl
'
' "グローバル変数の定義(Future BASIC CD-ROMおよびAdobe Systems提供のドキュメントから)"
' "Plug-InのドキュメントやヘッダーはNiftyServe/Adobejからダウンロードできます"
'
'------------------------------------------------------------
' "フォトショップ側から要求される処理の番号です"
'------------------------------------------------------------
_PSFAbout = 0:                                    ' "フィルタのアバウト画面の表示"
_PSFGetParameters = 1:                            ' "パラメーターの入力を行う場合"
_PSFPrepare = 2:                                  ' "フィルタの初期設定要求"
_PSFStart = 3:                                    ' "フィルタ処理の開始"
_PSFContinue = 4:                                 ' "フィルタ処理の実行"
_PSFFinish = 5:                                   ' "フィルタ処理の終了処理"
'------------------------------------------------------------
' "フォトショップの画像モード"
'------------------------------------------------------------
_PSModeBitmap = 0:                                ' "白黒モード"
_PSModeGrayScale = 1:                             ' "グレースケール"
_PSModeIndexedColor = 2:                          ' "インデックスカラー"
_PSModeRGBColor = 3:                              ' "RGBカラー"
_PSModeCMYKColor = 4:                             ' "CMYKカラー"
_PSModeHSLColor = 5:                              ' "HSLカラー"
_PSModeHSBColor = 6:                              ' "HSBカラー"
_PSModeMultichannel = 7:                          ' "マルチチャンネル"
_PSModeDuotone = 8:                               ' "ダブルトーン(デュオトーン)"
_PSModeLabColor = 9:                              ' "Labカラー"
'------------------------------------------------------------
' "フォトショップのホスト番号。フォトショップは1になります"
'------------------------------------------------------------
_PSFCheckHost = 1
'------------------------------------------------------------
' "エラーコード"
'------------------------------------------------------------
_PSFBadParms = -30100:                            ' "パラメーターエラー"
_PSFBadMode = -30101:                             ' "画像モードが違う場合のエラー"
_PSFCancel = 1:                                   ' "キャンセル(フィルタ処理の中止)"
'------------------------------------------------------------
' "フィルタ情報"
'------------------------------------------------------------
_PSFilterType = _"8BFM":                          ' "プラグインフィルタのファイルタイプ"
_PSFilterCreator = _"8BIM":                       ' "クリエーター(フォトショップは8BIM)"
_PSGroupRsrcType = _"PiMI":                       ' "メニュー項目に表示されるフィルタ項目"
_PSSupportedVersion = 4:                          ' "フィルターバージョン。フォトショップのバージョンではありません"
'------------------------------------------------------------
' "フィルタ情報の予備のバイト数"
'------------------------------------------------------------
_PSReserved1Bytes = 232
'------------------------------------------------------------
' "フォトショップとプラグインの間でやりとりするためのレコード"
' "(レコードはC言語では構造体にあたります)"
'------------------------------------------------------------
DIM RECORD PSFilterRec
  DIM PSSerialNum&:                               ' "フォトショップのシリアル番号"
  DIM PSAbortProc&:                               ' "中断処理の実行アドレス"
  DIM PSProgressProc&:                            ' "プログレスバー(進行状況表示)の実行アドレス"
  DIM PSParmsH&:                                  ' "パラメーターハンドル"
  DIM PSImageHt%:                                 ' "画像の縦の長さ"
  DIM PSImageWd%:                                 ' "画像の横の長さ"
  DIM PSPlanes%:                                  ' "画像のプレーン数(αチャンネルも含まれます)"
  DIM PSFilterRect.8:                             ' "フィルタ処理する矩形座標"
  DIM PSBackground.RGBColor:                      ' "背景色"
  DIM PSForeground.RGBColor:                      ' "前景色"
  DIM PSMaxSpace&:                                ' "最大バッファサイズ"
  DIM PSBufferSpace&:                             ' "バッファサイズ"
  DIM PSInRect.8:                                 ' "ピクセルデータ入力矩形座標"
  DIM PSInLoPlane%:                               ' "フィルタ処理する開始プレーン番号(読み込み)"
  DIM PSInHiPlane%:                               ' "フィルタ処理する終了プレーン番号(読み込み)"
  DIM PSOutRect.8:                                ' "ピクセルデータ出力座標"
  DIM PSOutLoPlane%:                              ' "フィルタ処理する開始プレーン番号(書き込み)"
  DIM PSOutHiPlane%:                              ' "フィルタ処理する終了プレーン番号(書き込み)"
  DIM PSInData&:                                  ' "入力画像のデータポインタ"
  DIM PSInRowBytes&:                              ' "rowBytes(入力側)"
  DIM PSOutData&:                                 ' "出力画像のデータポインタ"
  DIM PSOutRowBytes&:                             ' "rowBytes(出力側)"
  DIM PSIsFloating;1:                             ' "フローティング範囲かどうか。フローティング範囲ならtrue"
  DIM PSHaveMask;1:                               ' "マスクがあるかどうか。あるならばtrue"
  DIM PSAutoMask;1:                               ' "マスク範囲かどうか。マスク範囲ならtrue"
  DIM PSFiller1;1
  DIM PSMaskRect.8:                               ' "マスクデータ矩形座標"
  DIM PSMaskData&:                                ' "マスクデータのデータポインタ"
  DIM PSMaskRowBytes&:                            ' "マスク領域のrowBytes"
  
  ' "以下はバージョン4フィルタで拡張された部分です。フォトショップ2.5.1以降みたいです。"
  DIM PSBackground2&
  DIM PSForeground2&
  DIM PSHostSignature&:                           ' "ホストのシグネーチャー"
  DIM PSServices&
  DIM PSImageMode%:                               ' "画像モード"
  DIM PSImageHRes&:                               ' "画像の水平の解像度(ディスプレイは72dpiです)"
  DIM PSImageVRes&:                               ' "画像の垂直の解像度(ディスプレイは72dpiです)"
  DIM PSFloatCoord.4:                             ' "フローティング範囲の矩形座標"
  DIM PSWholeSize.4
  DIM PSMonitorInfo;0:                            ' "モニタ情報(ガンマ設定等)"
  DIM PSGamma&:                                   ' "ガンマ(以下に続く)"
  DIM PSRedX&, PSRedY&:                           ' "赤"
  DIM PSGreenX&, PSGreenY&:                       ' "緑"
  DIM PSBlueX&, PSBlueY&:                         ' "青"
  DIM PSWhiteX&, PSWhiteY&:                       ' "白"
  DIM PSAmbient&:                                 ' "環境光"
  DIM PSPlatformData&:                            ' "機種別データ。Macは0になります。"
  DIM PSBufferProcs&
  DIM PSResourceProcs&
  DIM PSProcessEventProc&:                        ' "プロセスイベント処理ルーチンのアドレス"
  DIM PSDisplayPixelsProc&
  DIM PSHandleProcs&
  DIM PSReserved1.PSReserved1Bytes:               ' "予備"
DIM END RECORD .PSFilterRec
※psNoise.Main
GOTO "Main":                                      ' "メインルーチンにジャンプ(忘れるといきなり最初の関数を実行してしまいます)"
'-------------------------- 定数 -------------------------------
_resID = 1000:                                    ' "リソース番号。リソースにはフィルタ項目を設定しておきます。あとアバウト画面も"
_About = 128:                                     ' "アバウト画面(フォトショップから呼び出される)"
' -------------------- コンパイラ設定 --------------------------
RESOURCES "psNoise.res", "8BFM8BIM", "8BFM", _resID, "ランダムノイズ"
OUTPUT FILE "ランダムノイズ"
COMPILE 0, _AppendRes_StrResource_DIMmedVarsOnly
GLOBALS "psHeader.glbl":                          ' "グローバルヘッダーファイルをリンク(くっつけます)"
' -------------------- "グローバル変数" --------------------------
DIM psCommand%:                                   ' "フォトショップから要求される処理の番号の格納用"
DIM FilterInfoPtr&:                               ' "フィルタの情報ヘッダーのポインタ"
DIM MyDataPtr&:                                   ' "フィルタプログラムで使用するグローバル変数のポインタ(今回は0)"
DIM ResultPtr&:                                   ' "フィルタ側での実行結果を格納するためのポインタ"
END GLOBALS
' -------------------- "フィルタ処理の終了" --------------------------
LOCAL MODE
LOCAL FN TerminateFiltering (FilterInfoPtr&)
  CALL SETRECT (#FilterInfoPtr& + _psInRect, 0, 0, 0, 0)
  CALL SETRECT (#FilterInfoPtr& + _psOutRect, 0, 0, 0, 0):' "こうしないとフィルタ処理が終了しない"
END FN
'-------------------------------------------------------------------
' "フィルタ処理がキャンセルされたかどうか"
' "ここはCD-ROMに収録されているプログラムをそのまま使用しました"
'-------------------------------------------------------------------
LOCAL MODE
LOCAL FN TestAbort% (FilterInfoPtr&)
  REGISTER (a0) = FilterInfoPtr&.psAbortProc&
  `              subq.l      #2,sp
  `              jsr         (a0)
  `              moveq       #0,d0
  `              move.b      (sp)+,d0
END FN
'-------------------------------------------------------------------
' "プログレスバー(進行状況)の処理"
' "ここはCD-ROMに収録されているプログラムをそのまま使用しました"
'-------------------------------------------------------------------
LOCAL MODE
LOCAL FN UpdateProgress% (FilterInfoPtr&, WorkDone&, WorkTotal&)
  REGISTER (a0) = FilterInfoPtr&.psProgressProc&
  `              move.l      ^WorkDone&,-(sp)
  `              move.l      ^WorkTotal&,-(sp)
  `              jsr         (a0)
END FN
'========================================================
' "1ラインづつ処理する。とりあえず乱数で適当に描画"
' "独自の処理を行いたい場合は、ここの関数を書き換えましょう"
'========================================================
LOCAL MODE
LOCAL FN OneLineProc% (FilterInfoPtr&, GlobalsHndl&)
  DIM outputPixMap&
  DIM LX%, i%,j%
  
  outputPixMap& = FilterInfoPtr&.psOutData&:      ' "書き込むデータ先のアドレスを求める"
  LX% = FilterInfoPtr&.psOutRect.Right% - FilterInfoPtr&.psOutRect.Left%:' "横の長さを求める"
  FOR j% = 1 TO  LX%:                             ' "横の長さだけ繰り返す"
    FOR i% = 1 TO  FilterInfoPtr&.psPlanes%:      ' "プレーン数分繰り返す"
      POKE outputPixMap&, INT(RND(255)):          ' "ランダム値を書き込む(全てのプレーン)"
      outputPixMap& = outputPixMap& + 1:          ' "書き込み先を1バイト増やす。すると次のプレーンを示す"
    NEXT
  NEXT
END FN
'-------------------------------------------------------------------
' "1ラインづつ画像データを読み込む指定とキャンセルチェックを行う"
'-------------------------------------------------------------------
LOCAL MODE
LOCAL FN NextFilterStep (FilterInfoPtr&, GlobalsHndl&)
  LONG IF FilterInfoPtr&.psInRect.Bottom% < FilterInfoPtr&.psFilterRect.Bottom%
    CALL OFFSETRECT (FilterInfoPtr&.psInRect%, 0, 1):' "1ライン下に移動"
    FilterInfoPtr&.psOutRect%;8 = FilterInfoPtr& + _psInRect:' "次に読み込む矩形範囲を指定"
  XELSE
    FN TerminateFiltering (FilterInfoPtr&):       ' "キャンセルされたか?"
  END IF
END FN
'-------------------------------------------------------------------
' "フィルタの開始処理"
'-------------------------------------------------------------------
LOCAL MODE
LOCAL FN DoStart (FilterInfoPtr&, MyDataPtr&)
  DIM GlobalsHndl&, ParmsH&
  
  ' "データ読み込み範囲を設定"
  CALL SETRECT (FilterInfoPtr&.psInRect%, FilterInfoPtr&.psFilterRect.Left%, FilterInfoPtr&.psFilterRect.Top%, FilterInfoPtr&.psFilterRect.Right%, FilterInfoPtr&.psFilterRect.Top% + 1)
  FilterInfoPtr&.psInLoPlane% = 0:                ' "読み込みプレーンの最初の番号"
  FilterInfoPtr&.psInHiPlane% = FilterInfoPtr&.psPlanes% - 1:' "最後のプレーンまで読み込む(プレーン数-1)"
  FilterInfoPtr&.psOutRect%;8 = FilterInfoPtr& + _psInRect:' "書き出す矩形座標も入力範囲と同じ"
  FilterInfoPtr&.psOutLoPlane% = FilterInfoPtr&.psInLoPlane%:' "書き出すプレーンの最初の番号を設定(入力プレーンと出力プレーンは一緒)"
  FilterInfoPtr&.psOutHiPlane% = FilterInfoPtr&.psInHiPlane%:' "書き出す最後の番号を設定(プレーン数-1)"
  'ParmsH& = FilterInfoPtr&.psParmsH&:             ' "パラメーターハンドルを設定"
  & MyDataPtr&, 0:                                ' "グローバル変数は使用しないので0(NIL)にしておく"
END FN
'-------------------------------------------------------------------
' "フィルタ処理の継続/実行"
'-------------------------------------------------------------------
LOCAL MODE
LOCAL FN DoContinue (FilterInfoPtr&, MyDataPtr&)
  DIM WorkSoFar%, WorkTotal%
  
  LONG IF FN TestAbort% (FilterInfoPtr&) = _false:' "キャンセルされていなかった場合に処理をする"
    WorkSoFar% = FilterInfoPtr&.psInRect.Top% - FilterInfoPtr&.psFilterRect.Top%:' "フィルタ処理する縦の長さ"
    WorkTotal% = FilterInfoPtr&.psFilterRect.Bottom% - FilterInfoPtr&.psFilterRect.Top%:' "処理し終わった長さ(現在のY座標-最初のY座標)"
    FN UpdateProgress (FilterInfoPtr&, WorkSoFar%, WorkTotal%):' "プログレスバーの更新"
    FN OneLineProc% (FilterInfoPtr&, [MyDataPtr&]):' "フィルタ処理"
    FN NextFilterStep (FilterInfoPtr&, [MyDataPtr&]):' "次に読み込むフィルタの設定処理"
  END IF
END FN
'-------------------------------------------------------------------
' "メインルーチン。単純に呼び出しているだけ"
' "フォトショップから送られてきたコマンド(要求)に対して処理を分岐"
'-------------------------------------------------------------------
"Main"
ENTERPROC% (psCommand%, FilterInfoPtr&, MyDataPtr&, ResultPtr&)
  DIM err%:                                       ' "フィルターのエラー"
  
  % REGISTER (a4) + 6, _resID
  
  err% = _noErr:                                  ' "エラーコードをクリアしておきます"
  SELECT psCommand%
    CASE _PSFAbout:                               ' "フィルタプログラム用のアバウト画面の表示"
      err% = FN ALERT (_About, 0):                ' "アラートをリソースエディタで作成しておきましょう"
    CASE _PSFGetParameters:                       ' "ダイアログを表示してパラメーターを入力する場合の処理"
      ' "今回はパラメーターは入力する必要がないので何もしません"
    CASE _PSFPrepare:                             ' "初期設定"
      ' "別にやる事はないので何もしません"
    CASE _PSFStart:                               ' "フィルタ処理の開始"
      FN DoStart (FilterInfoPtr&, MyDataPtr&)
    CASE _PSFContinue:                            ' "フィルタ処理を行う"
      FN DoContinue (FilterInfoPtr&, MyDataPtr&)
    CASE _PSFFinish:                              ' "フィルタの処理が完了した!"
      ' "別にやる事はないので何もしません"
    CASE ELSE
      err% = _PSFBadParms:                        ' "対応していないコマンドの時はエラーを返す"
  END SELECT
  % ResultPtr&, err%:                             ' "エラーコードを書き込みます"
  
EXITPROC%