プログラム講座 中級編7

- 画像加工アプリケーションを作成する -

 中級編7です。今回は中級編のまとめという事で、今まで作ってきた関数をまとめて「画像加工」アプリケーションを作成してみます。




◆今回のプログラム
 今回新しく作成する部分は画像を加工する部分です。オフスクリーンの画像を直接操作するというのは前回やりました。画像加工は結果が目に見えるので作っていて楽しいものです。しかし、Macでとなると話は別でいろいろ知らなければならない事があります。この中級編7を読んでいる方はすでに、画像加工アプリケーションを作成するだけの事はできるはずです。
 今回のプログラムは、各自でいろいろな加工ルーチンを作成してもらうため、「上下反転」のフィルタしか用意しません。




◆設計の重要さ
 今回は今までのプログラムをくっつける(コピー&ペースト)するだけでできてしまいます。そこで、今までの講座ではほとんど触れられていない「設計の重要さ」について書きたいと思います。
 今回のプログラムは「コピー&ペーストしてくっつけていく」程度でできあがりました。従来の形式のBASICでは、このような事は得意ではありませんでした。なぜ、コピー&ペーストができるかというと「関数が独立」しているためです。簡単に言うと「グローバル変数を使用しない」という事になります。
 全ての関数が必要であった場合、どの関数も必要なため他のアプリケーションにコピーして使うという事が難しくなってしまいます。



◆使い回し(ライブラリ)
 関数をグローバル変数に依存しないようにしておけば「使い回し」をする事ができます。さらに、動作は保証されているわけですから、バグが発生しても新しく作成した部分だけテストすればよいので楽ですし開発時間も短縮できます。このように、自前のプログラムをためておき「ライブラリ」にしておくと、さらに開発効率は良くなります。
 ライブラリをためるのが面倒だという場合が多いでしょう。幸いFBには標準でそのようなサンプル(関数)がいくつか用意されていますので、それを使うようにすればよいでしょう。また、素早くプログラムを作成したい人はProgram Generatorを使用するのもいいでしょう。ただし、PGのマニュアルは、もっとなんとかならないものかと思いますがf(-.-b



◆プログラム設計
 このプログラムは一体どういう動作をし、何を求めるのか。これが基本設計の根幹の部分です。これを元に関数を作成していきます。プログラムの設計というとフローチャートやPADなどがありますが、特にそのようなものを覚える必要はありません。それが中規模であろうと大規模であろうと不要でしょう。
 どういう具合にしプログラムを作ったらよいか分からない、という人が結構多いかもしれません。まず作るべきものがない時は、当然プログラムする必要はありません。作るべきものができた時、始めてプログラムの必要性がでてきます。
 今回のプログラムであれば、だいたい裏紙にでも以下のように書いておけばいいでしょう。

画像加工ソフト (以下は必要なもの)

 これに沿って今度は細かく分けます例えば仮想画面の設定であれば

仮想画面の設定

 これを順番にプログラムしていけばいいのです。あと勘違いしてはいけないのが、最初から全部このように書く事はありません。途中で、これが要りそうだな、と思ったら項目に追加すればいいのです。また、格好よくエディタやワープロなどで出力する必要もないでしょう(会社ではそうはいかないでしょうが。でも大抵の所はフローチャートとかで設計しているのではないでしょうか)。手書きで自分のわかるように書けばOK!です。すでに存在する関数や使い回す部分に関しては、深く書く必要もないので「仮想画面の設定」という一言で十分です。逆にこのようにして、新しい部分に力を入れていきます。



◆基本設計は重要
 小規模なアプリケーションであれば、基本設計が間違っていても作成し直す事ができますが、大規模なプログラムではそうはいきません。小さいプログラムをいくつか作成していくと「プログラムは正常に動作している。だが、使いにくい、機能を追加しにくい」などという事があるでしょう。つまりプログラム以前に「基本設計」がまずいのです。基本設計が良ければ、そのソフトは長く使用に耐えることが出来ます。どんな機能を盛り込むか、バージョンアップはどうするのか、などなど作っていくうちに「こうしてはいけない」「こうした方が結果的に良い」という事がわかってくると思います。
 ちなみに基本設計がしっかりしていないと、つぎはぎプログラムになってしまい機能追加もバグフィックスも難しくなって、まさに「泥沼」になってしまいます。では、「泥沼」になってしまった時どうするか? これには大変良い解決策があります。それは「作りなおす事」です。つまり基本設計から見直して作成した方が結果的によいものができますし、時間的にも速く完成できます。



◆終わりに
 プログラムを作成していると、わからない部分が必ずでてきます。そのような状況に陥ってしまったら、NiftyServeであればFMACPROなどで質問すると良いでしょう。もちろん、ある程度の状況とプログラムの動作などを書いて発言しなければ駄目です。あとはまわりに仲間(ネットワーク)を作っておく事です。自分がアドバイスできるようになれば初級プログラマを脱して見事中級プログラマになったと思ってよいのではないでしょうか。



◆今回のプログラムリスト
'----------------------------------------------- ' "仮想画面(オフスクリーン)のrowBytesを求めて直接描画する" '----------------------------------------------- DIM offScreen&,cport&,rect;8 DIM header%(256) rowBytes% = 0: ' "オフスクリーンのrowBytes" GRAM& = 0: ' "オフスクリーンのアドレス" ImageX = 320: ' "画像の横の長さ" ImageY = 240: ' "画像の縦の長さ" offScreen& = 0: ' "0の時は確保されていない!" R = 0 G = 0 B = 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 ' ----------------------------------------------- ' "オフスクリーンを確保する" ' offScreen& = "オフスクリーンのアドレス" ' ----------------------------------------------- LOCAL FN setOffscreen LONG IF offScreen& > 0 CALL DISPOSEGWORLD(offScreen&) WINDOW CLOSE #1 WINDOW #1,"Image Effecter",(16,45)-(16+ImageX,45+ImageY),_docNoGrow END IF CALL SETRECT(rect,0,0,ImageX,ImageY): '"320x240の画面を作成" err% = FN NEWGWORLD(offScreen&,32,rect,0,0,0) LONG IF err% BEEP BEEP END: ' "多くの場合、メモリ不足" END IF FN getRowBytes: ' "rowBytesを求める" END FN '------------------------------------------------------------- ' "PICTファイルをオープンしてオフスクリーンに描画する" '------------------------------------------------------------- LOCAL FN openPictFile DIM rectPICT;8 f$ = FILES$(_fOpen,"PICT",,vRefNum%) LONG IF f$<>"" OPEN "I",#1, f$,,vRefNum% fileSize& = LOF(1,1) pictHandle& = FN NEWHANDLE(fileSize&+4) LONG IF pictHandle& err = FN HLOCK(pictHandle&) LONG IF err = 0 READ FILE#1, [pictHandle&], fileSize& BLOCKMOVE [pictHandle&]+512,[pictHandle&],fileSize& - 512 err = FN HUNLOCK(pictHandle&) err = FN SETHANDLESIZE(pictHandle&, fileSize&-512) err = FN HLOCK(pictHandle&) rectPICT;8 = [pictHandle&]+_picFrame ImageX = rectPICT.right ImageY = rectPICT.bottom '---------------------------------------------------- FN setOffscreen: ' "オフスクリーンを確保する!" CALL SETGWORLD(offScreen&,0): '"オフスクリーンに切り替える" CALL DRAWPICTURE(pictHandle&,rectPICT) CALL SETGWORLD(cport&,0): '"ウィンドウに切り替える" '---------------------------------------------------- err = FN HUNLOCK(pictHandle&) END IF err = FN DISPOSHANDLE(pictHandle&) XELSE BEEP END IF CLOSE #1 END IF END FN '-------------------------------- ' Copy Offscreen -> Window '-------------------------------- CLEAR LOCAL LOCAL FN transfer LONG IF offScreen& > 0 CALL SETRECT(rect,0,0,ImageX,ImageY) CALL COPYBITS(#offScreen&+2,#cport&+2,rect,rect,_srcCopy,0) END IF END FN '----------------------------------------------- ' "オフスクリーンのカラーを読み出す" '----------------------------------------------- LOCAL FN myPOINT(x,y) LONG IF rowBytes% > 0 adrs& = GRAM& + y*rowBytes% + x*4: '"32ビットオフスクリーンなので1pixel=4byte。そのため4倍している" R = PEEK(adrs&+1): ' "32ビットオフスクリーンはaRGB順に並んでいる。" G = PEEK(adrs&+2) B = PEEK(adrs&+3) END IF END FN '----------------------------------------------- ' "オフスクリーンに点を表示する" '----------------------------------------------- LOCAL FN myPSET(x,y) 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 '-------------------------------------------------------- ' "自前で画面を消去する" '-------------------------------------------------------- LOCAL FN myCLS R = 255 G = 255 B = 255 FOR y = 0 TO ImageY-1 FOR x = 0 TO ImageX-1 FN myPSET(x,y) NEXT x NEXT y END FN '=============================================== ' "今回作成した部分(上下反転)" '=============================================== LOCAL FN upDownReverse LONG IF ImageY > 1 FOR y = 0 TO (ImageY-1)/2 FOR x = 0 TO ImageX-1 FN myPOINT(x,y) saveR = R saveG = G saveB = B FN myPOINT(x,(ImageY-1)-y) FN myPSET(x,y) SWAP saveR,R SWAP saveG,G SWAP saveB,B FN myPSET(x,(ImageY-1)-y) NEXT NEXT END IF BEEP: ' "加工が終了したことをビープ音で知らせる!" FN transfer: ' "できあがった画像を転送する" END FN '-------------------------------------------------------- ' "Pict画像の保存" '-------------------------------------------------------- LOCAL FN savePict DEF OPEN "PICT": ' "ファイルタイプをPICTにする" CALL SETRECT(rect,0,0,ImageX,ImageY) saveFile$ = FILES$(_fSave,"保存ファイル名:","名称未設定",volRefNum%) LONG IF LEN(saveFile$) OPEN "O",#1,saveFile$,,volRefNum% CALL SETGWORLD(offScreen&,0): ' "描画側をオフスクリーン側に" savePicture& = FN OPENPICTURE(rect) COLOR _zWhite CALL COPYBITS(#offScreen&+2,#offScreen&+2,rect,rect,_srcCopy,0) CALL CLOSEPICTURE CALL SETGWORLD(cport&,0): ' "描画側を元に戻す" WRITE FILE #1,@header%(0),512: ' "Header Write" bytes& = FN GETHANDLESIZE(savePicture&) err% = FN HLOCK(savePicture&) WRITE FILE #1,[savePicture&],bytes& CLOSE #1 CALL KILLPICTURE(savePicture&) END IF END FN '-------------------------------------------------------- ' "アップデートなどのイベントを取得する" '-------------------------------------------------------- LOCAL FN doDialog evnt = DIALOG(0) id = DIALOG(evnt) SELECT evnt CASE _wndRefresh FN transfer: '"アップデートイベントなので画面を再描画がする" END SELECT END FN '-------------------------------------------------------- ' "メニューを構築する" '-------------------------------------------------------- LOCAL FN initMenu 'File Menu MENU 1,0,_enable,"ファイル" MENU 1,1,_enable,"/O開く..." MENU 1,2,_enable,";" MENU 1,3,_enable,"/S名前を付けて保存..." MENU 1,4,_enable,";" MENU 1,5,_enable,"/Q終 了" MENU 2,0,_enable,"加 工" MENU 2,1,_enable,"上下反転" END FN '--------------------------------------------- ' "メニューの選択" '--------------------------------------------- LOCAL FN doMenus menuID = MENU(_menuID) itemID = MENU(_itemID) SELECT menuID CASE 1 : ' File Menu SELECT itemID CASE 1: FN openPictFile CASE 3: FN savePict CASE 5: ' Quit... CALL DISPOSEGWORLD(offScreen&) END END SELECT CASE 2: SELECT itemID CASE 1: FN upDownReverse: ' "加工メニューの上下反転" END SELECT END SELECT MENU END FN WINDOW OFF WINDOW #1,"Image Effecter",(16,45)-(16+320,45+240),_docNoGrow CALL GETPORT(cport&): ' "ウィンドウのグラフポートを確保しておきます" ON MENU FN doMenus ON DIALOG FN doDialog FN initMenu FN setOffscreen FN myCLS: '"画面を消去する" FN transfer DO HANDLEEVENTS UNTIL theProgramEnds END