プログラム講座 上級編4
DTPの鬼門 - 2色分解プラグインを作成する -
 上級編4です。
 今回はDTPの鬼門とも言える2色分解を行うフォトショップのプラグインについて解説します。手に負えないと思ったら読むのをやめた方がいいと思います。今回は別の知識が必要ですので・・・
◆DTP(デスクトップパブリッシング)
 Macintoshとイメージセッター(昔はタイプセッターと言われた事もあります)/レーザープリンタ(Laser Writer)によって印刷業界は大きく変化しました。Macを使用したDTPが急速に普及したのはつい3〜4年前からです。今もこの傾向は続いているというべきか、そうならざるを得ない状況に追い込まれている写植屋さん、印刷屋さんが多いのではないでしょうか。
 DTPの利点は手軽にクオリティの高いものが作成できる云々と言われますが、実は最も安く4色印刷ができます。4色印刷(またはフルカラー印刷。特色は含まない)の場合、CMYKの4色つまり
 をうまく混合してフルカラーに見せています。当然4つの版が必要になりますので白黒とくらべてコストがかかります。
 世の中には「白黒では駄目だが4色で刷る金は払えない」という需要が存在します。これが「2色印刷」と呼ばれるもので、誰でも必ずといっていいほど目にした事があるはずです。2色印刷はスーパーマーケットの広告、ビラ、ちらしなどに使用され新聞にたくさん折り込まれてきます。バリエーションはいくつかありますが、基本的には混合して黒になる色の組み合わせで印刷されています。多くは「赤と緑」「シアンとマゼンダ」のどちらかでしょう。食料品の場合は、野菜の緑と肉の赤を掛け合わせて表現しますので「赤と緑」の色の組み合わせはおきまりパターンです。
 「4色ができるなら2色もできるよね」
 とQuarkXpress, Page Makerでやってみた人もいるでしょう。ところが、うまくいきません。うまくいかない原因はいくつかありますが、聞いた話では米国では2色印刷をしないようです(もしくは少ない)。両方のソフトとも米国生まれですので当然2色分解のオプションなどはありません。
 残る有力なソフトはPhotoshopでしょう。Photoshopにはダブルトーン(デュオトーン)と呼ばれる画像モードがあります。画像を一度グレースケールに変換後、このモードを選択すると版数(1〜4版)まで選択する事が出来ます。早速やってみると、どうも違います。それもそのはず、ダブルトーンモードは数色混合して作成されたインキの「1色」の輝度で画像を表現するためです。つまり根本的に2色印刷とはかけはなれているわけです。
 QuarkXpress, Page Maker, Photoshopでできないとなると、当然手を付けてはならない領域、つまる所「DTPの鬼門」(笑)と化してしまうわけです。実際に書籍を探してみても4色もしくはモノクロについて書かれていても2色印刷について書かれているものはありません(販売されていない本で例外的にはあります)。
◆CMYとCMYK
 安価なカラープリンタはCMYの3色しかなく黒(スミ版と呼ばれます)のインキは存在しません。それでも黒色は黒色として印刷されてきます。3色を混ぜ合わせれば理論的には黒になるのでもっともと言えば最もです。それでは逆になぜ黒色(スミ版)が必要なのでしょうか? それはCMYで印刷された黒色とCMYKで印刷された黒色を見比べれば簡単にわかります。CMYKで印刷されたものの方が黒が鮮明で他の色もしまって見えるためです。黒だけでなく他の色も変わるのには理由があります。それはCMYを黒色に割り当て分散させるためです。これは下色除去法(UCRと略される場合もあります。PostScriptリファレンスではUCRと表記されています)と呼ばれ、色成分を他の色に分散させ綺麗に見せるというものです。
 この色をどのくらい割り当てるかは、どうも経験則で行われているようです。残念ながら手元に資料がないので、どのくらいなのかはわかりません。
◆3色から4色へ、3色から2色へ
 今回作成するのはフォトショップのプラグインですのでフォトショップの画像モードに大きく左右されます。フォトショップにはCMYKモードはあってもCMYモードはありません(マルチチャンネルで代用する方法も考えられますが)。つまり色数を落としていくにはCMYK -> CMY -> CMと段階的に考えていけば大丈夫なはずです。実際の印刷屋さんの話しでは2色印刷の場合イエローの版は捨ててしまうそうなのでCMY -> CMという1版を削除するだけでも使えなくはありません。
 まずCMYKからCMYに変換する方法を考えてみます。先ほどのUCRは他の版を黒に割り当てていますので、この逆を行います。2色印刷では黒はありませんので、この黒色をUCRと同じく全部CMYに割り当ててしまいます。この割り当て演算式は[(C*K)/最大輝度]となりフォトショップの8ビットチャンネルであれば255となります。C,M版にK版を分散させれば一応終わりですがY版もM版に加味しています。このためマゼンダが強くでる傾向にあります。強すぎると思ったらY版を加味するのをやめてみてください。
 ここで分解できるのはシアンとマゼンダのみです。赤色と緑色も分解できますが残念ながら普通の方法では実際の仕上がりを確認する事はできません。素直に2色分解用アプリケーションを作成した方が良さそうです。また、2色印刷は結構経験がものを言うらしいので、素人が簡単に手出しできる領域ではないのかもしれません。
◆終わりに
 短い説明でしたが、わからない人には難解な文章だったかもしれません。通常Macでは2色分解は行われませんが、このプラグインでちょっと試してみるのもよいかもしれません。注意点としては画像モードをチェックしていないので、必ずCMYKモードにしておかなければならないという事です。
 今回のような2色分解であれば、まだ良いのですが特色2色分解となるとさすがに面倒になります。特色2色なんて滅多に使わないと言われそうですが、同人誌では結構使われていますf(^^; なんといっても最もコストを安くして最大の効果を引き出したいのですから当然と言えば当然です。もっとも、複雑な分解はせずにまぜあわせない特色指定ばかりですが。フルカラー印刷も安くなったのでフルカラーの表紙のものも多くあります。また、今年からセブンイレブンでカラーコピーサービスを始めたので、なおさら2色印刷は遠くなっていくのかもしれません。
分解サンプル
 このプラグインで分解したものを載せておきます。
| 元 画 像(MZ MailingListのメンバーです^^;) | 
  | 
| CMYKからY,Kだけを取り除いたもの | 
  | 
| Y,Kだけを取り除きレベル補正したもの | 
  | 
| 今回の2色分解プラグインを使用したもの | 
  | 
◆今回のプログラムリスト
※psHeader.glblは上級編2と全く同じものを使用します。
※Sepate2.Main
GOTO "Main":                                      ' "メインルーチンにジャンプ(忘れるといきなり最初の関数を実行してしまいます)"
'-------------------------- 定数 -------------------------------
_resID = 1000:                                    ' "リソース番号。リソースにはフィルタ項目を設定しておきます。あとアバウト画面も"
_About = 128:                                     ' "アバウト画面(フォトショップから呼び出される)"
' -------------------- コンパイラ設定 --------------------------
RESOURCES "separate.res", "8BFM8BIM", "8BFM", _resID, "2色分解"
OUTPUT FILE "2色分解"
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
'========================================================
' "ここで色分解を行います"
' "UCRされた黒成分をCMに割り当てY成分をMに割り当てます"
' "CMYKモードでないと動きません、あしからず"
'========================================================
LOCAL MODE
LOCAL FN OneLineProc% (FilterInfoPtr&, GlobalsHndl&)
  DIM outputPixMap&,inputPixMap&
  DIM LX%, i%,j%,planeNum%
  DIM C%,M%,Y%,K%
  
  inputPixMap& = FilterInfoPtr&.psInData&
  outputPixMap& = FilterInfoPtr&.psOutData&:      ' "書き込むデータ先のアドレスを求める"
  LX% = FilterInfoPtr&.psOutRect.Right% - FilterInfoPtr&.psOutRect.Left%:' "横の長さを求める"
  planeNum% = FilterInfoPtr&.psPlanes%:           ' "プレーン数をあらかじめ求めておく(CMYKの4プレーン)"
  FOR j% = 1 TO  LX%:                             ' "横の長さだけ繰り返す"
    C% = PEEK(inputPixMap& + 0):                  ' "シアン"
    M% = PEEK(inputPixMap& + 1):                  ' "マゼンダ"
    Y% = PEEK(inputPixMap& + 2):                  ' "イエロー"
    K% = PEEK(inputPixMap& + 3):                  ' "ブラック"
    C% = INT((C%*K%)/255)
    M% = INT(  ((( M% * K% ) / 255) * (Y%/2)) /128 )
    Y% = 255
    K% = 255
    POKE outputPixMap&, C%
    POKE outputPixMap&+1, M%
    POKE outputPixMap&+2, Y%
    POKE outputPixMap&+3, K%
    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%