プログラム講座 上級編3
- Photoshopプラグインフィルタの作成 Part 2-
 上級編3です。
 今回も前回にひき続きフォトショップのプラグインフィルタを作成します。前回は単純にノイズを加えるだけのものでしたが、通常のプラグインの場合渡された画像データを元に加工し、それをフォトショップに返すという具合になっています。データをフォトショップに渡す方法は前回やりましたので、今回はフォトショップから渡される画像データを加工して渡すものを作成してみます。今回は短いのでご安心を(^^)
◆画像加工
 コンピューターのプログラムの中で特に画像関係は「目に見えて分かる」ためか書籍も多くあります。画像加工と一口に言っても、前回のようなノイズをふりまくだけのものから、円形マッピング、モザイク、ぼかしなど非常に数多くのバリエーションがあります。
 今回は、役に立たないものを作ってみます。何をするプラグインかというと、「赤色の成分の輝度を半分にする」というものです。複雑なものよりも簡単なサンプルであれば理解するのも優しいでしょう。という事で、今回はこれに決まり(笑)。
◆どこの画像データが渡されるのか?
 フォトショップは非常に多機能です。プラグインは2.5.1以降どれでも一応動作する仕組みになっています(新たに追加された機能を使わなければ)。プラグインの進歩はともかく、フォトショップ2.5.1から3.0、そして4.0へ次々とバージョンアップし、αチャンネルだけでなくレイヤーやレイヤーマスク、調整レイヤーなど、どんどん機能が増えてきています。
 ここで、問題となるのは、どういう部分がプラグインに渡されるのか?という事です。2.5.1ではレイヤー機能がありませんので、画像モードに応じた「チャンネルデータ」がそのまま送られてきます。ところが3.0では「レイヤー」機能の追加により「選択されたレイヤーの画像のチャンネルデータ」が送られてきます。全てのレイヤーの情報は送られてこないわけです。つまり、「一番上のレイヤーと一番下のレイヤーのチャンネルを入れ替える」といった事はできないという事になります。ただし、最新のドキュメントがないため、このような事ができるとしても、どうしようもありません。無理な処理はやめておきましょう。
◆実際のプログラム
 最初にプログラムを載せておきます。前回ノイズをまきちらした部分と同じです。
LOCAL MODE
LOCAL FN OneLineProc% (FilterInfoPtr&, GlobalsHndl&)
  DIM outputPixMap&,inputPixMap&
  DIM LX%, i%,j%,pixels%,planeNum%
  
  inputPixMap& = FilterInfoPtr&.psInData&
  outputPixMap& = FilterInfoPtr&.psOutData&:      ' "書き込むデータ先のアドレスを求める"
  LX% = FilterInfoPtr&.psOutRect.Right% - FilterInfoPtr&.psOutRect.Left%:' "横の長さを求める"
  planeNum% = FilterInfoPtr&.psPlanes%:           ' "プレーン数をあらかじめ求めておく"
  FOR j% = 1 TO  LX%:                             ' "横の長さだけ繰り返す"
    pixels% = PEEK(inputPixMap& + 0):             ' "赤のプレーンなので0(本当はモードによって違うけど手抜き)"
    POKE outputPixMap&, INT(pixels% / 2):         ' "輝度を半分にする"
    inputPixMap& = outputPixMap& + planeNum%:     ' "入力するピクセルを右側に1つずらす"
    outputPixMap& = outputPixMap& + planeNum%:    ' "出力するピクセルを右側に1つずらす"
  NEXT
END FN
 太字で示した部分が前回と異なる部分、つまりデータを読み込む部分です。送られてくるデータは前回説明しましたので、前回の解説を参照してください。
 チャンネル分だけデータが送られてきます。赤色の成分は一番最初に送られてきます。これは送られてくるデータが以下のようになっているためです(RGBモードのみ)。
+0 : R (0〜255)
+1: G(0〜255)
+2: B(0〜255)
+3: α1(0〜255)
+4: α2(0〜255)
+5: α3(0〜255)
  :
  :
 αチャンネルがない場合は+3以降のデータは送られてきません。チャンネル数はどうやって求めるかというと、
PLANE% = FilterInfoPtr&.psPlanes%
 とすればPLANE%にチャンネル数が入ります。肝心のデータの読み込みは、次のようになります。
inputPixMap& = FilterInfoPtr&.psInData&
 これでinputPixMap&に入力する画像データのアドレスが入ります。チャンネルデータ(画像データ)を読み込む場合は、
R% = PEEK(inputPixMap&)
 とすればR%にチャンネルデータ(画像データ)が入ります。後は、このR%をR% = INT(R%/2)のようにしてやれば輝度が半分になります。他のチャンネルデータ(画像データ)を読み出すものも参考がてら載せておきます。ただし、RGBモードのみです(画像モードが替わるとチャンネルの意味が異なるため)。
赤の値を読み出す
R% = PEEK(inputPixMap&)
緑の値を読み出す
G% = PEEK(inputPixMap&+1)
青の値を読み出す
B% = PEEK(inputPixMap&+2)
チャンネル4(αチャンネル1)の値を読み出す
A% = PEEK(inputPixMap&+3)
◆終わりに
 これで最低限データを入力して加工するという事ができるようになりました。後、残っている部分はダイアログを表示させてユーザーからのデータ入力を待つといった処理です。これは、サンプルのCD-ROMに収録されているので、そちらを参考にしてください。プラグインばかりやっていると、時間がかかってしまって、他の部分に進めないので、とりあえずプラグインはこのくらいにして終わりたいと思います。
 次は中級編で「ドラッグ&ドロップ」をやりたいと思います。
◆今回のプログラムリスト
※psHeader.glblは上級編2と全く同じものを使用します。
※psBright.Main
GOTO "Main":                                      ' "メインルーチンにジャンプ(忘れるといきなり最初の関数を実行してしまいます)"
'-------------------------- 定数 -------------------------------
_resID = 1000:                                    ' "リソース番号。リソースにはフィルタ項目を設定しておきます。あとアバウト画面も"
_About = 128:                                     ' "アバウト画面(フォトショップから呼び出される)"
' -------------------- コンパイラ設定 --------------------------
RESOURCES "psBright.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&,inputPixMap&
  DIM LX%, i%,j%,pixels%,planeNum%
  
  inputPixMap& = FilterInfoPtr&.psInData&
  outputPixMap& = FilterInfoPtr&.psOutData&:      ' "書き込むデータ先のアドレスを求める"
  LX% = FilterInfoPtr&.psOutRect.Right% - FilterInfoPtr&.psOutRect.Left%:' "横の長さを求める"
  planeNum% = FilterInfoPtr&.psPlanes%:           ' "プレーン数をあらかじめ求めておく"
  FOR j% = 1 TO  LX%:                             ' "横の長さだけ繰り返す"
    pixels% = PEEK(inputPixMap& + 0):             ' "赤のプレーンなので0(本当はモードによって違うけど手抜き)"
    POKE outputPixMap&, INT(pixels% / 2):         ' "輝度を半分にする"
    inputPixMap& = outputPixMap& + planeNum%:     ' "入力するピクセルを右側に1つずらす"
    outputPixMap& = outputPixMap& + planeNum%:    ' "出力するピクセルを右側に1つずらす"
  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%