プログラム講座 中級編6
- オフスクリーンに直接描画する -
中級編6です。今回はQuickDrawを呼び出さずに、自前で直接オフスクリーンに描画してみます。
「QuickDrawは多機能だから直接描画する必要はないのでは?」
中にはそう思う人もいるかもしれません。が、Future BASIC等のマニュアルを見てもらえばわかりますが、意外とQuickDrawは多機能ではなく基本的なものしか用意されていません。例えば画像を上下反転させたい場合、いくらQuickDrawの呼び出しを見ても、そんなものは用意されていません。自前でやるしかありません。こういう場合、どうしても直接オフスクリーンに描画する、オフスクリーンを読む必要があります。
今回は昔懐かしい黒いバックに星(点)を描くというプログラムを作成してみます。
◆オフスクリーンに直接描画するための知識
オフスクリーンに直接描画するためにはいくつか知っておかなければならない事柄があります。また、QuickDrawを使う場合は比較的安全ですが、直接オフスクリーンを扱うとなると危険が伴います。間違えてもシステムを破壊する恐れはほとんどありませんが、バスエラー等は避けられないでしょう。どこでも描画できますが、リスクもあるという事です。もちろん、ちゃんとチェックをしていれば、このような事は起こりません。
それではオフスクリーンに直接描画するための説明をします。
◆色数とバイト並び
Macintoshは以下の色数のオフスクリーンを使用する事ができます。
- 白黒(1)
- 4色(2)
- 16色(4)
- 256色(8)
- 32768色(16)
- 1677万色(32)
カッコ内はオフスクリーンを確保する時に指定するビット数です。オフスクリーンを確保するとオフスクリーンの中身は指定したビット数(色数)に応じて変化します。つまり白黒とフルカラーではオフスクリーンの中身(メモリですね)の並び方が異なるという事です。文章だけではわかりずらいので図にすると以下のようになります。
赤い|は1ピクセルの境界を示しています。つまり白黒であれば1バイトで8ピクセル(ドット)表せます。フルカラーであれば1ピクセル表すのに4バイト必要だ(実際に必要なのは3バイトだが)という事です。これらの関係は重要です。これを知らないとオフスクリーンを直接操作する事はできません。よく覚えておいて下さい。
◆rowBytesとは?
Macintoshで直接オフスクリーンを扱うときに必ず出てくる用語があります。それがrowBytesです。
例えば256色320*240のオフスクリーンを確保したとしましょう。オフスクリーンの中身(メモリ内容)はいったいどうなるでしょう。普通に考えれば320*240=64000バイトだと思います。所が実際に確保してみるとこれよりも大きな値になります。実はオフスクリーンを確保すると都合のいいように切りのよいバイト数(16の倍数など)で横幅を合わせてしまうのです。つまり横のサイズが320ピクセルだからといって次の行の先頭がその次のバイト(321)だ、という保証はありません。保証どころか、まずありえないでしょう。
この横1ラインの総バイト数の事をrowBytesと読んでいます。つまり次の行の先頭の場所(アドレス)を求めるにはrowBytes分だけ乗算しないと求める事ができないのです。図にすると以下のような感じになります。
オフスクリーンを確保したときに、一体rowBytesはどのように演算されるのか、その計算式もInside Macintoshに掲載されていますが、実際にはそのような計算は行わずに、ある所から読み出して使用します。
◆rowBytesを読み出すには?
オフスクリーンを確保した時にその情報はグラフポートポインタ(プログラム中ではoffScreen&です)に入ります。この中にrowBytesがちゃんと入っています。つまり、この場所から単純に読み込めばOKです。さらにFuture BASICでは_rowBytesという定数が用意されています。
ところが実際問題として過去の互換性の問題などから面倒な処理をしなければいけません。基本的には以下の処理をしないと正常にrowBytesを読み出すことができません。
- オフスクリーンのグラフポートから実際のオフスクリーンへのハンドルを読み出す
- 読み出したハンドルをロックする(専用の呼び出しを使います)
- ハンドルの内容が示すメモリから_rowBytes加算しrowBytesを読み込む
といった具合になります。実際のプログラムは6行しかありませんが、大抵の場合まわりくどい方法で説明するのでわからなくなってしまうのかもしれません。が、やはり危険を伴うのでちゃんと注意事項を読んで気を付けて使用した方がよいでしょう。
◆実際のオフスクリーンのアドレスを読み出すには?
なんとかrowBytesは求める事ができましたが、肝心のオフスクリーンが格納されているメモリの場所(アドレス)がわからない事には書き込みようがありません。
オフスクリーンを確保した時にオフスクリーンへのハンドルが得られるのだから、そのハンドルの内容を読み出せばいいのではないか?と思うでしょうが、こうなると泥沼です。実はアドレスを読み出すには専用の呼び出しを使用しないと駄目なのです。実際には以下のように呼び出してアドレスを取得しなければ駄目です。
GRAM& = FN GETPIXBASEADDR(PixMapH&)
これでGRAM&にアドレスが入ります。
ご苦労様でした。これで、やっと準備OKです。プログラムは6行ですが説明は長いですね(^^;
◆描画するアドレスを出すには?
ここまで来ればあとは簡単です。自前でPSET関数(オフスクリーンに点をうつ)を作ってみましょう。直接オフスクリーンを操作する場合は、わざわざSETPORTやSETGWORLDを使う必要はありません。ある意味で非常に便利です(^^)
指定したX座標とY座標から書き込むべきアドレスを求めてみましょう。こんな具合になります。
adrs& = GRAM& + y*rowBytes% + x*4
adrs&が書き込むべきアドレスです。今回は32ビットオフスクリーンを使用していますのでXを4倍しています(1ピクセル=4バイト)。これが256色ならばx*4はxだけでOKです。32768色ならばx*2になります。色数を変えて実験してみるとよくわかると思います。
◆終わりに
自前PSET関数ができてしまえば後は応用でできるでしょう。今回のサンプルプログラムは、黒バックに星を描画するという単純なものです。
これでオフスクリーンを直接操作できるようになりましたので次回は画像を読み込んで加工し保存するというプログラムを作成してみましょう。
◆今回のプログラムリスト
'-----------------------------------------------
' "仮想画面(オフスクリーン)のrowBytesを求めて直接描画する"
'-----------------------------------------------
DIM offScreen&,cport&,rect;8
rowBytes% = 0: ' "オフスクリーンのrowBytes"
GRAM& = 0: ' "オフスクリーンのアドレス"
END GLOBALS
'-----------------------------------------------
' "オフスクリーンのrowBytesを求める"
'-----------------------------------------------
LOCAL FN getRowBytes
PixMapH& = FN GETGWORLDPIXMAP(offScreen&)
err% = FN LOCKPIXELS(PixMapH&)
LONG IF err%
GRAM& = FN GETPIXBASEADDR(PixMapH&)
rowBytes% = {[PixMapH&] + _rowBytes} AND &H3FFF
END IF
END FN
'-----------------------------------------------
' "オフスクリーンに点を表示する"
'-----------------------------------------------
LOCAL FN myPSET(x,y,R,G,B)
LONG IF rowBytes% > 0
adrs& = GRAM& + y*rowBytes% + x*4: '"32ビットオフスクリーンなので1pixel=4byte。そのため4倍している"
POKE adrs&,0 : '"未使用領域(フォトショップ等ではαチャンネル保存用として使用される事もある)"
POKE adrs&+1,R: ' "32ビットオフスクリーンはaRGB順に並んでいる。"
POKE adrs&+2,G
POKE adrs&+3,B
END IF
END FN
' -----------------------------------------------
' "オフスクリーンを確保する"
' offScreen& = "オフスクリーンのアドレス"
' -----------------------------------------------
CLEAR LOCAL
LOCAL FN setOffscreen
CALL SETRECT(rect,0,0,320,240): '"320x240の画面を作成"
err% = FN NEWGWORLD(offScreen&,32,rect,0,0,0)
LONG IF err%
BEEP
END: ' "多くの場合、メモリ不足"
END IF
END FN
'--------------------------------
' Copy Offscreen -> Window
'--------------------------------
CLEAR LOCAL
LOCAL FN transfer
CALL SETRECT(rect,0,0,320,240)
CALL COPYBITS(#offScreen&+2,#cport&+2,rect,rect,_srcCopy,0)
END FN
'--------------------------------------------------------
' "自前で画面を消去する"
'--------------------------------------------------------
LOCAL FN myCLS
FOR y = 0 TO 239
FOR x = 0 TO 319
FN myPSET(x,y,0,0,0)
NEXT x
NEXT y
END FN
'--------------------------------------------------------
' Display Character
'--------------------------------------------------------
LOCAL FN drawChar
FOR i=0 TO 80
draw_R = RND(256)
draw_G = RND(256)
draw_B = RND(256)
draw_x = RND(320)
draw_y = RND(240)
FN myPSET(draw_x,draw_y,draw_R,draw_G,draw_B)
NEXT
END FN
LOCAL FN initMenu
'File Menu
MENU 1,0,_enable,"ファイル"
MENU 1,1,_enable,"/E画面消去"
MENU 1,2,_enable,"/Q終 了"
END FN
'---------------------------------------------
' "メニューの選択"
'---------------------------------------------
LOCAL FN doMenus
menuID = MENU(_menuID)
itemID = MENU(_itemID)
SELECT menuID
CASE 1 : ' File Menu
SELECT itemID
CASE 1:
FN myCLS
CASE 2: ' Quit...
CALL DISPOSEGWORLD(offScreen&)
END
END SELECT
END SELECT
MENU
END FN
WINDOW OFF
WINDOW #1,"Main Screen",(16,45)-(16+320,45+240),_dialogMovable
CALL GETPORT(cport&): ' "ウィンドウのグラフポートを確保しておきます"
ON MENU FN doMenus
FN initMenu
FN setOffscreen
FN getRowBytes: ' "オフスクリーンのrowBytesを求める"
FN myCLS: '"画面を消去する"
DO
HANDLEEVENTS
FN drawChar
FN transfer
UNTIL theProgramEnds
END