プログラム講座 中級編15
- お絵かきソフトを作ろう(その4) -
カット、コピー、ペーストの処理

 中級編15です。今回は「カット」「コピー」「ペースト」「消去」の処理について解説します。今回の処理を入れることで、一応ペイントソフトとしての機能は入ることになります。ただ、インターフェースは史上最低に近いので、処理は参考にしても構いませんが、インタフェースは間違っても真似しないで下さい(^^;
◆クリップボードとは?
 クリップボードはデータを「カット」「コピー」「ペースト」する時に使用する「一時的なデータ格納エリア(メモリ)」です。一時的なものですので電源を落とせば内容は消えてしまいます。Macは、このクリップボードを介する事によってアプリケーション間でテキスト、画像、サウンド等を何も考えずにコピーしたりペーストして使用する事ができます。最近ではドラッグドロップによって同様にデータの受け渡しをクリップボードを介さずに行う事が出来るようになっています。
 クリップボードの処理を行うツールボックスは「スクラップマネージャー」です。アップルメニューにある「スクラップブック」とは違いますので注意して下さい(^^)
 FB2ハンドブックマニュアルの275頁にスクラップマネージャーのツールボックスコールがありますが、わずかに6つしかありません。このうち実際に使用するのは上に記述されている3つくらいです。後はクリップボードの内容をチェックするものくらいです。実際に使用するツールボックスは以下の3つです。
| クリップボードの内容を消す | err% = FN ZEROSCRAP | 
| クリップボードのデータを取得する | dataSize& = FN GETSCRAP(dataH&,type&,dataOffset&) | 
| クリップボードにデータを転送する | err% = FN PUTSCRAP(dataSize&,type&,dataPointer&) | 
dataSize&:取得したデータのサイズ
type&:データタイプ(PICT,TEXT等)
dataOffset&:データオフセット
dataPointer&:クリップボードに転送するデータのポインタ
◆コピー処理
 コピーを行う場合、オフスクリーンの画像ハンドルを取得する必要があります。というのもクリップボードに画像データを書き込むためには、実際のデータが格納されているアドレスを指定しなければならないためです。いきなりオフスクリーンを指定するとエラーの憂き目にあいますので「PICT画像の保存」と同様にPICTデータをOPENPICTURE、COPYBITS等を利用してハンドルを作成します。
 作成されたハンドルが取得できればデータの格納されているアドレスも取得できます。コピーを行う前にクリップボードを消去してから書き込みます。クリップボードの内容を消去するには、
err% = FN ZEROSCRAP
 のようにします。あとはハンドルをロックして
err% = FN PUTSCRAP(PICTsize&,_"PICT",[copyPicture&])
 として画像データをクリップボードに転送します。データタイプを指定する場合は_"4文字のデータタイプ"のように指定した方が楽です。頑張って32ビット(4バイト)指定しても結果は同じですがf(^^;
 コピー処理のリストを以下に示しておきます。
'--------------------------------------------------------
'   "コピーメニューの処理"
'   "画像をクリップボードに転送します"
'--------------------------------------------------------
CLEAR LOCAL
LOCAL FN theCopy
  DIM rect;8
  
  CALL SETGWORLD(gOffScreen&,0):                  ' "操作するオフスクリーンを設定します"
  CALL SETRECT(rect,0,0,gImageX,gImageY):         ' "コピーする矩形サイズを設定します"
  copyPicture& = FN OPENPICTURE(rect):            '"ピクチャーハンドルを作成し、記録開始"
  COLOR _zBlack
  CALL COPYBITS(#gOffScreen&+2,#gOffScreen&+2,rect,rect,_srcCopy,0):'"自分自身に書き込む(お約束)"
  CALL CLOSEPICTURE:                              '"記録終了"
  LONG COLOR gB*256,gG*256,gR*256
  CALL SETGWORLD(cport&,0):                       ' "描画側を元に戻します"
  
  clipBoad& = FN ZEROSCRAP
  PICTsize& = FN GETHANDLESIZE(copyPicture&):     ' "画像のハンドルサイズを求めます"
  err% = FN HLOCK(copyPicture&):                  ' "ハンドルをロックします"
  IF err% = _noErr THEN err% = FN PUTSCRAP(PICTsize&,_"PICT",[copyPicture&])
  err% = FN HUNLOCK(copyPicture&):                ' "ハンドルロックを解除"
  CALL KILLPICTURE(copyPicture&):                 '"ピクチャーハンドルは、もういらないので抹殺"
END FN
◆ペースト処理
 ペースト処理はコピーの処理と逆を行うだけです。が、ペーストした場合ペーストされる画像のサイズに合わせてウィンドウを変更するか、現在のウィンドウに合うようにするか、それとも原寸大でペーストするか決めておく必要があります。今回は特にウィンドウに合わせてサイズを変更したりする事はせずにオープンされているウィンドウに原寸大で描画しています。ここらへんは、好みに合わせて改造してみるとよいでしょう。本当は範囲を選択して移動できるようにすると一番良いのですが、そこは各自勉強して作成してみてください(と言って逃げる^^;)。
 コピー処理と違ってペースト処理は数カ所気を付ける点があります。まず実際のプログラムを見てみましょう。
  offset& = 0
  copyPICT& = FN NEWHANDLE(0):                    ' "0バイトのハンドルを確保"
  PICTsize& = FN GETSCRAP(copyPICT&,_"PICT",offset&):' "一番先頭にある画像を取得します"
 offset&はデータのオフセットですが、これはクリップボードに入っているデータが複数(複合データ)ある場合は考慮しなければなりません。通常の使用では0バイトにしておけばよいでしょう。0バイトと決まっているならば直接ゼロを指定してもよさそうなものですが、FN GETSCRAPで指定するとエラーになってしまいます。
 次に取得するデータサイズはFN INFOSCRAPで調べない限りわかりません。このような場合は、0バイトのハンドルだけを確保します。こうしておいてFN GETSCRAPを呼び出せば自動的に必要なサイズのメモリを確保してくれます。
 ペースト処理のプログラムリストを以下に示します。
'--------------------------------------------------------
'   "ペーストメニューの処理"
'   "画像をクリップボードから転送します"
'--------------------------------------------------------
CLEAR LOCAL
LOCAL FN thePaste
  DIM rect;8
  
  offset& = 0
  copyPICT& = FN NEWHANDLE(0):                    ' "0バイトのハンドルを確保"
  PICTsize& = FN GETSCRAP(copyPICT&,_"PICT",offset&):' "一番先頭にある画像を取得します"
  
  LONG IF PICTsize&
    err% = FN HLOCK(copyPICT&):                   ' "PICTハンドルをロック!"
    LONG IF err% = _noErr
      FN UNDO
      err% = FN HLOCK(copyPICT&):                 ' "ハンドルをロック!"
      rect;8 = [copyPICT&]+_picFrame:             ' "PICTの画像の矩形を取り出す"
      CALL SETGWORLD(gOffScreen&,0):              '"オフスクリーンに切り替える"
      CALL DRAWPICTURE(copyPICT&,rect):           ' "オフスクリーンに描画します"
      CALL SETGWORLD(cport&,0):                   '"ウィンドウに切り替える"
      err% = FN HUNLOCK(copyPICT&)
    END IF
    err% = FN DISPOSHANDLE(copyPICT&):            ' "PICTハンドルを破棄"
  END IF
  FN transfer
END FN
◆カット処理、消去処理
 カット処理は「コピー処理」を呼び出して画像を白色にするだけです。同様に消去処理は画像を白色にするだけです。コピー処理、ペースト処理ができてしまえば、後は簡単にできます。
 今回は範囲を選択したりする事ができませんので、「全てを選択」といったメニューは用意してありますが選択不可にしてあります。
◆終わりに
 非常に長い期間でペイントソフトの解説を行ってきました。本当は今回作成したようなペイントソフトは通常のペイントソフトからは逸脱しています。画像加工アプリケーションを改造してペイントソフトに仕立てたのが、間違いといえば間違いですが、その割に簡単に作成する事ができました。C言語で作成していたら、頭を抱え込んでいた可能性もあります(笑)
 ここまでで通常のペイントソフトで必要となる部分の大半については解説したつもりです。保存する画像形式は常にPICTのみ、印刷はできないといった部分もありますが、一応ペイントソフトの中身は、こんな具合いというのが分かればよいかなと思います。
 次回は今まで扱ってこなかった「マルチウィンドウ処理」について説明します。次回から作成するのは簡易エディタで2回にわけて解説する予定です。

◆今回のプログラムリスト
'----------------------------------------------------
' "Bad Paint   ...1997 Program By KaZuhiro FuRuhata"
'----------------------------------------------------
RESOURCES "about.res":                            ' "リソースファイルを読み込む"
'--------------------- "定数"-------------------------
_fileMenu = 1:                                    ' "ファイルメニュー"
_editMenu = 2:                                    ' "エディット(編集)メニュー"
_effectMenu = 3:                                  ' "加工メニュー"
_penSizeMenu = 4:                                 ' "ペンサイズメニュー"
_penModeMenu = 5:                                 ' "ペンモード"
_colorMenu = 6:                                   ' "カラーメニュー"
_fileOpen = 1:                                    ' "ファイルメニュー:開く"
_fileSave = 3:                                    ' "ファイルメニュー:保存"
_fileQuit = 5:                                    ' "ファイルメニュー:終了"
_editUndo = 1:                                    ' "エディットメニュー:取り消し"
_editCut = 3:                                     ' "エディットメニュー:カット"
_editCopy = 4:                                    ' "エディットメニュー:コピー"
_editPaste = 5:                                   ' "エディットメニュー:ペースト"
_editClear = 6:                                   ' "エディットメニュー:クリア"
_upDownEffect = 1:                                ' "上下反転"
_colorBlack = 1:                                  ' "黒色"
_colorBlue = 2:                                   ' "青色"
_colorRed = 3:                                    ' "赤色"
_colorMagenta = 4:                                ' "紫色"
_colorGreen = 5:                                  ' "緑色"
_colorCyan = 6:                                   ' "水色"
_colorYellow = 7:                                 ' "黄色"
_colorWhite = 8:                                  ' "白色"
_colorPick = 9:                                   ' "カラーピッカーで任意の色を選択"
_penCursor = 128:                                 ' "ペンカーソルのリソース番号"
'----------------- "グローバル変数"-------------------
DIM cport&
gOffScreen& = 0:                                  ' "0の時は確保されていない!"
gBackScreen& = 0
gSwapScreen& = 0
gRowBytes% = 0:                                   ' "オフスクリーンのrowBytes"
gGRAM& = 0:                                       ' "オフスクリーンのアドレス"
gImageX = 320:                                    ' "画像の横の長さ"
gImageY = 240:                                    ' "画像の縦の長さ"
gR = 0:                                           '"赤色"
gG = 0:                                           '"緑色"
gB = 0:                                           '"青色"
gQuit_flag = _false:                              '"終了フラグ"
penSize% = 0:                                     ' "ペンサイズ"
penMode% = _patCopy:                              ' "ダイレクトコピーモード"
dragFlag% = _false:                               ' "アンドゥチェック用"
END GLOBALS:                                      ' "グローバル変数定義の終了宣言"
'-----------------------------------------------
' "オフスクリーンのrowBytesを求める"
'-----------------------------------------------
CLEAR LOCAL
LOCAL FN getRowBytes
  PixMapH& = FN GETGWORLDPIXMAP(gOffScreen&):     ' "オフスクリーンの画像ハンドルを求める"
  err% = FN LOCKPIXELS(PixMapH&):                 ' "画像ハンドルをロック!"
  LONG IF err%
    gGRAM& = FN GETPIXBASEADDR(PixMapH&):         ' "画像が格納されている先頭のアドレスを求める"
    gRowBytes% = {[PixMapH&] + _rowBytes} AND &H3FFF:' "rowBytesを求める"
  END IF
END FN
' -----------------------------------------------
'  "オフスクリーンを確保する"
' gOffScreen& = "オフスクリーンのアドレス"
' -----------------------------------------------
CLEAR LOCAL
LOCAL FN setOffscreen
  DIM rect;8
  
  LONG IF gOffScreen& > 0
    CALL DISPOSEGWORLD(gOffScreen&):              ' "オフスクリーンを破棄"
    CALL DISPOSEGWORLD(gBackScreen&):             ' "オフスクリーンを破棄"
    CALL DISPOSEGWORLD(gSwapScreen&):             ' "オフスクリーンを破棄"
    WINDOW CLOSE #1:                              ' "ウィンドウを閉じて、新しいウィンドウを開く"
    WINDOW #1,"Bad Paint",(16,45)-(16+gImageX,45+gImageY),_docNoGrow
  END IF
  CALL SETRECT(rect,0,0,gImageX,gImageY):         '"320x240の画面を作成"
  err% = FN NEWGWORLD(gOffScreen&,32,rect,0,0,0):' "オフスクリーンを確保する"
  IF err% THEN BEEP:BEEP:END
  FN getRowBytes:                                 ' "rowBytesを求める"
  
  err% = FN NEWGWORLD(gBackScreen&,32,rect,0,0,0):' "オフスクリーンを確保する(アンドゥ用退避)"
  IF err% THEN BEEP:BEEP:END
  
  err% = FN NEWGWORLD(gSwapScreen&,32,rect,0,0,0):' "オフスクリーンを確保する(スワップ画面)"
  IF err% THEN BEEP:BEEP:END
END FN
'--------------------------------
' "オフスクリーンからウィンドウへ転送"
'--------------------------------
CLEAR LOCAL
LOCAL FN transfer
  DIM rect;8
  
  LONG IF gOffScreen& > 0
    CALL SETRECT(rect,0,0,gImageX,gImageY):       ' "転送サイズを設定"
    CALL COPYBITS(#gOffScreen&+2,#cport&+2,rect,rect,_srcCopy,0):' "オフスクリーンからウィンドウに転送!"
  END IF
END FN
'--------------------------------
' "アンドゥ処理"
'--------------------------------
CLEAR LOCAL
LOCAL FN UNDO
  DIM rect;8
  LONG IF gOffScreen& > 0
    CALL SETRECT(rect,0,0,gImageX,gImageY):       ' "転送サイズを設定"
    CALL COPYBITS(#gOffScreen&+2,#gSwapScreen&+2,rect,rect,_srcCopy,0):' "表画面 -> スワップ"
    CALL COPYBITS(#gBackScreen&+2,#gOffScreen&+2,rect,rect,_srcCopy,0):' "裏画面 -> 表画面"
    CALL COPYBITS(#gSwapScreen&+2,#gBackScreen&+2,rect,rect,_srcCopy,0):' "スワップ -> 表画面"
  END IF
  FN transfer
END FN
'--------------------------------
' "表画面を裏画面にコピーする (Back -> Off)"
'--------------------------------
CLEAR LOCAL
LOCAL FN toBackScreen
  DIM rect;8
  LONG IF gOffScreen& > 0
    CALL SETRECT(rect,0,0,gImageX,gImageY):       ' "転送サイズを設定"
    CALL COPYBITS(#gOffScreen&+2,#gBackScreen&+2,rect,rect,_srcCopy,0):' "表画面 -> 裏画面"
  END IF
END FN
'-----------------------------------------------
' "オフスクリーンのカラーを読み出す"
'-----------------------------------------------
CLEAR LOCAL
LOCAL FN myPOINT(x,y)
  IF (x<0) OR (x>=gImageX) OR (y<0) OR (y>=gImageY) THEN EXIT FN
  LONG IF gRowBytes% > 0
    adrs& = gGRAM& + y*gRowBytes% + x*4:          '"32ビットオフスクリーンなので1pixel=4byte。そのため4倍している"
    gR = PEEK(adrs&+1):                           ' "32ビットオフスクリーンはaRGB順に並んでいる。"
    gG = PEEK(adrs&+2)
    gB = PEEK(adrs&+3)
  END IF
END FN
'-----------------------------------------------
' "オフスクリーンに点を表示する"
'-----------------------------------------------
CLEAR LOCAL
LOCAL FN myPSET(x,y)
  IF (x<0) OR (x>=gImageX) OR (y<0) OR (y>=gImageY) THEN EXIT FN
  LONG IF gRowBytes% > 0
    adrs& = gGRAM& + y*gRowBytes% + x*4:          '"32ビットオフスクリーンなので1pixel=4byte。そのため4倍している"
    POKE adrs&,0 :                                '"未使用領域(フォトショップ等ではαチャンネル保存用として使用される事もある)"
    POKE adrs&+1,gR:                              ' "32ビットオフスクリーンはaRGB順に並んでいる。"
    POKE adrs&+2,gG
    POKE adrs&+3,gB
  END IF
END FN
'--------------------------------------------------------
' "自前で画面を消去する"
' "勉強用なので、非常に低速な方法でやってます"
'--------------------------------------------------------
CLEAR LOCAL
LOCAL FN myCLS
  gR = 255
  gG = 255
  gB = 255
  FOR y = 0 TO gImageY-1
    FOR x = 0 TO gImageX-1
      FN myPSET(x,y)
    NEXT x
  NEXT y
END FN
'-------------------------------------------------------------
' "PICTファイルをオープンしてオフスクリーンに描画する"
'-------------------------------------------------------------
CLEAR LOCAL
LOCAL FN openPictFile
  DIM rect;8
  
  f$ = FILES$(_fOpen,"PICT",,vRefNum%):           ' "ファイル選択ダイアログの表示"
  LONG IF f$<>""
    OPEN "I",#1, f$,,vRefNum%:                    ' "PICTファイルオープン"
    fileSize& = LOF(1,1):                         ' "ファイルサイズを求める"
    pictHandle& = FN NEWHANDLE(fileSize&+4)
    LONG IF pictHandle&
      err = FN HLOCK(pictHandle&):                ' "PICTハンドルをロック!"
      LONG IF err = 0
        READ FILE#1, [pictHandle&], fileSize&:    ' "ファイルサイズ分だけファイルから読み込む"
        BLOCKMOVE [pictHandle&]+512,[pictHandle&],fileSize& - 512:' "先頭512バイトを消す"
        err = FN HUNLOCK(pictHandle&):            ' "ハンドルロック解除"
        err = FN SETHANDLESIZE(pictHandle&, fileSize&-512):' "メモリサイズを512減らす"
        err = FN HLOCK(pictHandle&):              ' "ハンドルをロック!"
        rect;8 = [pictHandle&]+_picFrame:         ' "PICTの画像の矩形を取り出す"
        gImageX = rect.right:                     ' "右側の座標を取り出す"
        gImageY = rect.bottom:                    ' "下側の座標を取り出す"
        '----------------------------------------------------
        FN setOffscreen:                          ' "オフスクリーンを確保する!"
        CALL SETGWORLD(gOffScreen&,0):            '"オフスクリーンに切り替える"
        FN myCLS:                                 ' "オフスクリーンの内容を消去する"
        FN toBackScreen:                          ' "アンドゥ用の画面を転送して白にする"
        CALL DRAWPICTURE(pictHandle&,rect)
        CALL SETGWORLD(cport&,0):                 '"ウィンドウに切り替える"
        '----------------------------------------------------
        err = FN HUNLOCK(pictHandle&)
      END IF
      err = FN DISPOSHANDLE(pictHandle&):         ' "PICTハンドルを破棄"
    XELSE 
      BEEP:                                       ' "ハンドルが確保できない〜エラーいこっちゃ"
    END IF
    CLOSE #1:                                     ' "ファイルを閉じる"
  END IF
END FN
'--------------------------------------------------------
'   "Pict画像の保存"
'--------------------------------------------------------
CLEAR LOCAL
LOCAL FN savePict
  DIM header%(256), rect;8
  
  DEF OPEN "PICT":                                ' "ファイルタイプをPICTにする"
  saveFile$ = FILES$(_fSave,"保存ファイル名:","名称未設定",volRefNum%)
  LONG IF LEN(saveFile$):                         '"ファイル名の長さが1以上、つまりファイル名が入力された場合"
    OPEN "O",#1,saveFile$,,volRefNum%:            '"保存するファイル名で新規に開く"
    CALL SETGWORLD(gOffScreen&,0):                ' "描画側をオフスクリーン側にします"
    CALL SETRECT(rect,0,0,gImageX,gImageY):       ' "保存するサイズを設定する"
    savePicture& = FN OPENPICTURE(rect):          '"ピクチャーハンドルを作成し、記録開始"
    COLOR _zBlack:                                ' "黒に設定します"
    CALL COPYBITS(#gOffScreen&+2,#gOffScreen&+2,rect,rect,_srcCopy,0):'"自分自身に書き込む(お約束)"
    CALL CLOSEPICTURE:                            '"記録終了"
    LONG COLOR gB*256,gG*256,gR*256:              ' "現在の描画色に戻します。"
    CALL SETGWORLD(cport&,0):                     ' "描画側を元に戻します"
    
    WRITE FILE #1,@header%(0),512:                ' "512バイトの空ヘッダーを書き込む"
    bytes& = FN GETHANDLESIZE(savePicture&):      '"ピクチャーハンドルのサイズを求める(Toolbox)"
    err% = FN HLOCK(savePicture&):                '"ハンドルロック"
    WRITE FILE #1,[savePicture&],bytes&:          '"まとめて一気に書き込む"
    CLOSE #1:                                     '"ファイルを閉じます"
    CALL KILLPICTURE(savePicture&):               '"ピクチャーハンドルは、もういらないので抹殺"
  END IF
END FN
'--------------------------------------------------------
'   "コピーメニューの処理"
'   "画像をクリップボードに転送します"
'--------------------------------------------------------
CLEAR LOCAL
LOCAL FN theCopy
  DIM rect;8
  
  CALL SETGWORLD(gOffScreen&,0):                  ' "操作するオフスクリーンを設定します"
  CALL SETRECT(rect,0,0,gImageX,gImageY):         ' "コピーする矩形サイズを設定します"
  copyPicture& = FN OPENPICTURE(rect):            '"ピクチャーハンドルを作成し、記録開始"
  COLOR _zBlack
  CALL COPYBITS(#gOffScreen&+2,#gOffScreen&+2,rect,rect,_srcCopy,0):'"自分自身に書き込む(お約束)"
  CALL CLOSEPICTURE:                              '"記録終了"
  LONG COLOR gB*256,gG*256,gR*256
  CALL SETGWORLD(cport&,0):                       ' "描画側を元に戻します"
  
  clipBoad& = FN ZEROSCRAP
  PICTsize& = FN GETHANDLESIZE(copyPicture&):     ' "画像のハンドルサイズを求めます"
  err% = FN HLOCK(copyPicture&):                  ' "ハンドルをロックします"
  IF err% = _noErr THEN err% = FN PUTSCRAP(PICTsize&,_"PICT",[copyPicture&])
  err% = FN HUNLOCK(copyPicture&):                ' "ハンドルロックを解除"
  CALL KILLPICTURE(copyPicture&):                 '"ピクチャーハンドルは、もういらないので抹殺"
END FN
'--------------------------------------------------------
'   "カットメニューの処理"
'   "画像をクリップボードに転送し画面を消去します"
'--------------------------------------------------------
CLEAR LOCAL
LOCAL FN theCut
  FN theCopy:                                     ' "コピーする"
  FN UNDO:                                        ' "取り消し処理"
  FN myCLS:                                       ' "画面を消去する"
  FN transfer
END FN
'--------------------------------------------------------
'   "消去(クリア)メニューの処理"
'   "画像を消します"
'--------------------------------------------------------
CLEAR LOCAL
LOCAL FN theClear
  FN UNDO:                                        ' "取り消し処理"
  FN myCLS:                                       ' "画面を消去する"
  FN transfer
END FN
'--------------------------------------------------------
'   "ペーストメニューの処理"
'   "画像をクリップボードから転送します"
'--------------------------------------------------------
CLEAR LOCAL
LOCAL FN thePaste
  DIM rect;8
  
  offset& = 0
  copyPICT& = FN NEWHANDLE(0):                    ' "0バイトのハンドルを確保"
  PICTsize& = FN GETSCRAP(copyPICT&,_"PICT",offset&):' "一番先頭にある画像を取得します"
  
  LONG IF PICTsize&
    err% = FN HLOCK(copyPICT&):                   ' "PICTハンドルをロック!"
    LONG IF err% = _noErr
      FN UNDO
      err% = FN HLOCK(copyPICT&):                 ' "ハンドルをロック!"
      rect;8 = [copyPICT&]+_picFrame:             ' "PICTの画像の矩形を取り出す"
      CALL SETGWORLD(gOffScreen&,0):              '"オフスクリーンに切り替える"
      CALL DRAWPICTURE(copyPICT&,rect):           ' "オフスクリーンに描画します"
      CALL SETGWORLD(cport&,0):                   '"ウィンドウに切り替える"
      err% = FN HUNLOCK(copyPICT&)
    END IF
    err% = FN DISPOSHANDLE(copyPICT&):            ' "PICTハンドルを破棄"
  END IF
  FN transfer
END FN
'===============================================
'   "上下反転"
'===============================================
CLEAR LOCAL
LOCAL FN upDownReverse
  LONG IF gImageY > 1
    FOR y = 0 TO (gImageY-1)/2:                   ' "半分までやれば上下が反転します"
      FOR x = 0 TO gImageX-1
        FN myPOINT(x,y)
        saveR = gR
        saveG = gG
        saveB = gB
        FN myPOINT(x,(gImageY-1)-y)
        FN myPSET(x,y)
        SWAP saveR,gR:                            ' "入れ替える変数の型(整数、文字)は同じでないと駄目です"
        SWAP saveG,gG
        SWAP saveB,gB
        FN myPSET(x,(gImageY-1)-y)
      NEXT
    NEXT
  END IF
  BEEP:                                           ' "加工が終了したことをビープ音で知らせる!"
  FN transfer:                                    ' "できあがった画像を転送する"
END FN
'--------------------------------------------------------
' "マウスイベントを処理する"
'--------------------------------------------------------
CLEAR LOCAL
LOCAL FN drawPoint
  DIM myPoint.4:                                  ' "マウス位置を取得するためのポイントレコードを用意"
  
  CALL GETMOUSE(myPoint):                         ' "マウス位置を取得(ローカル座標になります)"
  x% = myPoint.h%:                                ' "ウィンドウ上でのマウスのX座標"
  y% = myPoint.v%:                                ' "ウィンドウ上でのマウスのY座標"
  LONG IF FN BUTTON
    LONG IF ( (x% >=0 ) AND (x% < gImageX) ) AND ( (y% >= 0 ) AND (y% < gImageY) )
      IF dragFlag% = _false THEN FN toBackScreen:' "アンドゥ用画面にデータを退避"
      dragFlag% = _true
      CALL SETGWORLD(gOffScreen&,0):              ' "描画側をオフスクリーン側にします"
      CALL LINETO(x%,y%)
      CALL SETGWORLD(cport&,0):                   ' "描画側を元に戻します"
      FN transfer
    END IF
  XELSE
    CALL SETGWORLD(gOffScreen&,0):                ' "描画側をオフスクリーン側にします"
    CALL MOVETO(x%,y%):                           ' "ペン位置を移動させます"
    CALL SETGWORLD(cport&,0):                     ' "描画側を元に戻します"
    dragFlag% = _false
  END IF
  ' "カーソル位置がウィンドウ内かどうか調べる"
  LONG IF ( (x% >=0 ) AND (x% < gImageX) ) AND ( (y% >= 0 ) AND (y% < gImageY) )
    CURSOR _penCursor:                            ' "ペンカーソルに変更"
  XELSE
    CURSOR _arrowCursor:                          ' "通常の矢印カーソルに変更"
  END IF
END FN
'--------------------------------------------------------
' "ペンモードを設定する"
'--------------------------------------------------------
CLEAR LOCAL
LOCAL FN setPenMode(theMode%)
  CALL SETGWORLD(gOffScreen&,0):                  ' "描画側をオフスクリーン側にします"
  SELECT theMode%
    CASE 1:
      CALL PENMODE(_patCopy)
    CASE 2:
      CALL PENMODE(_patOr)
    CASE 3:
      CALL PENMODE(_patXor)
    CASE 4:
      CALL PENMODE(_patBic)
    CASE 5:
      CALL PENMODE(_notPatCopy)
    CASE 6:
      CALL PENMODE(_notPatOr)
    CASE 7:
      CALL PENMODE(_notPatXor)
    CASE 8:
      CALL PENMODE(_notPatBic)
  END SELECT
  CALL SETGWORLD(cport&,0):                       ' "描画側を元に戻します"
END FN
'--------------------------------------------------------
' "カラーピッカーを表示して色を選びます(^^)"
'--------------------------------------------------------
CLEAR LOCAL
LOCAL FN thePicker
  DIM oldRGB.rgbColor,newRGB.rgbColor
  DIM disp.4
  
  ' "カラーピッカーの表示位置を設定します"
  disp.v% = 100
  disp.h% = 100
  
  oldRGB.red% = gR*256:                           ' "RGBは16ビット指定"
  oldRGB.green% = gG*256:                         ' "現在のカラー値を設定"
  oldRGB.blue% = gB*256
  
  ' "カラーピッカーを表示(機能拡張により異なります)"
  ' "返り値が真(_TRUE)の時は選択された。偽(_false)の時はキャンセルされた"
  flag% = FN GETCOLOR(disp,"色を選んでね",oldRGB,newRGB)
  LONG IF flag%
    gR = newRGB.red% / 256
    gG = newRGB.green% / 256
    gB = newRGB.blue% / 256
  END IF
END FN
'--------------------------------------------------------
' "カラーを設定する"
'--------------------------------------------------------
CLEAR LOCAL
LOCAL FN setColor(theColor%)
  DIM oldRGB.rgbColor,newRGB.rgbColor
  DIM disp.4
  
  SELECT theColor%
    CASE _colorBlack:
      gR = 0
      gG = 0
      gB = 0
    CASE _colorBlue:
      gR = 0
      gG = 0
      gB = 255
    CASE _colorRed:
      gR = 255
      gG = 0
      gB = 0
    CASE _colorMagenta:
      gR = 255
      gG = 0
      gB = 255
    CASE _colorGreen:
      gR = 0
      gG = 255
      gB = 0
    CASE _colorCyan:
      gR = 0
      gG = 255
      gB = 255
    CASE _colorYellow:
      gR = 255
      gG = 255
      gB = 0
    CASE _colorWhite:
      gR = 255
      gG = 255
      gB = 255
    CASE _colorPick
      FN thePicker
  END SELECT
  CALL SETGWORLD(gOffScreen&,0):                  ' "描画側をオフスクリーン側にします"
  LONG COLOR gB*256,gG*256,gR*256
  CALL SETGWORLD(cport&,0):                       ' "描画側を元に戻します"
END FN
'--------------------------------------------------------
' "アップデートなどのイベントを取得する"
'--------------------------------------------------------
CLEAR LOCAL
LOCAL FN doDialog
  evnt = DIALOG(0)
  id = DIALOG(evnt):                              '"発生したイベントの種類"
  SELECT evnt
    CASE _wndRefresh:                             '"ウィンドウリフレッシュ(アップデートイベント)"
      FN transfer:                                '"アップデートイベントなので画面を再描画がする"
  END SELECT
END FN
'--------------------------------------------------------
' "アバウト画面の表示"
'--------------------------------------------------------
CLEAR LOCAL
LOCAL FN about
  err = FN ALERT(128,0)
END FN
'--------------------------------------------------------
' "メニューを構築する"
'--------------------------------------------------------
CLEAR LOCAL
LOCAL FN initMenu
  APPLE MENU "About Bad Paint..."
  
  '"ファイルメニュー"
  MENU _fileMenu,0,_enable,"ファイル"
  MENU _fileMenu,_fileOpen,_enable,"/O開く..."
  MENU _fileMenu,2,_enable,";"
  MENU _fileMenu,_fileSave,_enable,"/S名前を付けて保存..."
  MENU _fileMenu,4,_enable,";"
  MENU _fileMenu,_fileQuit,_enable,"/Q終 了"
  
  ' "クリップボード等のコピー&ペーストを行う場合は EDIT MENU 2 とします。
  MENU _editMenu,0,_enable,"編集"
  MENU _editMenu,_editUndo,_enable,"/Z取り消し"
  MENU _editMenu,2,_disable,";"
  MENU _editMenu,_editCut,_enable,"/Xカット"
  MENU _editMenu,_editCopy,_enable,"/Cコピー"
  MENU _editMenu,_editPaste,_enable,"/Vペースト"
  MENU _editMenu,_editClear,_enable,"消去"
  MENU _editMenu,7,_disable,";"
  MENU _editMenu,8,_disable,"/A全てを選択"
  
  ' "加工メニュー"
  MENU _effectMenu,0,_enable,"加 工"
  MENU _effectMenu,_upDownEffect,_enable,"上下反転"
  
  ' "ペンサイズメニュー"
  MENU _penSizeMenu,0,_enable,"ペンサイズ"
  MENU _penSizeMenu,1,_enable,"1"
  MENU _penSizeMenu,2,_enable,"2"
  MENU _penSizeMenu,3,_enable,"3"
  MENU _penSizeMenu,4,_enable,"4"
  MENU _penSizeMenu,5,_enable,"5"
  MENU _penSizeMenu,6,_enable,"6"
  MENU _penSizeMenu,7,_enable,"7"
  MENU _penSizeMenu,8,_enable,"8"
  MENU _penSizeMenu,9,_enable,"9"
  MENU _penSizeMenu,10,_enable,"10"
  MENU _penSizeMenu,11,_enable,"11"
  MENU _penSizeMenu,12,_enable,"12"
  MENU _penSizeMenu,13,_enable,"13"
  MENU _penSizeMenu,14,_enable,"14"
  MENU _penSizeMenu,15,_enable,"15"
  MENU _penSizeMenu,16,_enable,"16"
  
  ' "ペンモードメニュー"
  MENU _penModeMenu,0,_enable,"ペンモード"
  MENU _penModeMenu,1,_enable,"Copy"
  MENU _penModeMenu,2,_enable,"Or"
  MENU _penModeMenu,3,_enable,"Xor"
  MENU _penModeMenu,4,_enable,"Bic"
  MENU _penModeMenu,5,_enable,"NotCopy"
  MENU _penModeMenu,6,_enable,"NotOr"
  MENU _penModeMenu,7,_enable,"NotXor"
  MENU _penModeMenu,8,_enable,"NotBic"
  
  ' "ペンモードメニュー"
  MENU _colorMenu,0,_enable,"カラー"
  MENU _colorMenu,_colorBlack,_enable,"黒色"
  MENU _colorMenu,_colorBlue,_enable,"青色"
  MENU _colorMenu,_colorRed,_enable,"赤色"
  MENU _colorMenu,_colorMagenta,_enable,"紫色"
  MENU _colorMenu,_colorGreen,_enable,"緑色"
  MENU _colorMenu,_colorCyan,_enable,"水色"
  MENU _colorMenu,_colorYellow,_enable,"黄色"
  MENU _colorMenu,_colorWhite,_enable,"白色"
  MENU _colorMenu,_colorPick,_enable,"色選択..."
  
  DEF CHECKONEITEM(_penSizeMenu,1):               ' "ペンサイズの初期値は1"
  DEF CHECKONEITEM(_penModeMenu,1):               ' "COPYモード"
  DEF CHECKONEITEM(_colorMenu,1):                 ' "黒色"
  gR = 0
  gG = 0
  gB = 0
END FN
'---------------------------------------------
' "メニューの選択"
'---------------------------------------------
CLEAR LOCAL
LOCAL FN doMenus
  menuID = MENU(_menuID):                         '"選択されたメニューバー項目の番号"
  itemID = MENU(_itemID):                         '"プルダウンメニューで選択された項目番号"
  
  SELECT menuID
    CASE _appleMenu:                              ' "アバウト画面の表示(_appleMenuはあらかじめ定義されています)"
      FN about
    CASE _fileMenu :                              ' "ファイルメニュー"
      SELECT itemID
        CASE _fileOpen:                           ' "画像を読み込む(開く)"
          FN openPictFile
        CASE _fileSave:                           ' "画像の保存"
          FN savePict
        CASE _fileQuit:                           ' "終了が選択された"
          gQuit_flag = _true
      END SELECT
    CASE _editMenu:                               ' "編集メニュー"
      SELECT itemID
        CASE _editUndo:                           ' "アンドゥ(取り消し)"
          FN UNDO:                                ' "取り消し実行"
        CASE _editCut:                            ' "カットメニュー"
          FN theCut:                              ' "カット実行"
        CASE _editCopy:                           ' "コピーメニュー"
          FN theCopy:                             ' "コピー実行"
        CASE _editPaste:                          ' "コピーペースト"
          FN thePaste:                            ' "ペースト実行"
        CASE _editClear:                          ' "消去メニュー"
          FN theClear:                            ' "消去実行"
      END SELECT
    CASE _effectMenu:                             ' "加工メニュー"
      SELECT itemID
        CASE _upDownEffect:
          FN upDownReverse:                       ' "加工メニューの上下反転が選択された"
      END SELECT
    CASE _penSizeMenu:                            ' "ペンサイズメニュー"
      penSize% = itemID:                          ' "ペンサイズを設定"
      CALL SETGWORLD(gOffScreen&,0):              ' "描画側をオフスクリーン側にします"
      CALL PENSIZE(penSize%,penSize%):            ' "ペンサイズを設定"
      CALL SETGWORLD(cport&,0):                   ' "描画側を元に戻します"
      DEF CHECKONEITEM(_penSizeMenu,itemID):      ' "ペンサイズにチェックマーク"
    CASE _penModeMenu:                            ' "ペンモードメニュー"
      FN setPenMode(itemID):                      ' "ペンモードを設定します"
      DEF CHECKONEITEM(_penModeMenu,itemID):      ' "ペンモードにチェックマーク"
    CASE _colorMenu:                              ' "カラーメニュー"
      FN setColor(itemID):                        ' "カラーを設定"
      DEF CHECKONEITEM(_colorMenu,itemID):        ' "色にチェックマーク"
  END SELECT
  MENU:                                           ' "これがないとメニューバーの項目が強調表示されたままになってしまいます"
END FN
WINDOW OFF
WINDOW #1,"Bad Paint",(0,0)-(gImageX, gImageY),_docNoGrow
CALL GETPORT(cport&):                             ' "ウィンドウのグラフポートを確保しておきます"
ON MENU FN doMenus:                               '"メニューが選択された時の飛び先"
ON DIALOG FN doDialog:                            '"ダイアログイベントが発生した時の飛び先"
FN initMenu:                                      '"メニューの初期化"
FN setOffscreen:                                  '"オフスクリーンの確保"
FN myCLS:                                         '"画面を消去します"
FN toBackScreen:                                  ' "退避用画面もついでに転送して消去してしまいます"
FN transfer:                                      '"オフスクリーンからウィンドウへ画像を転送"
gR = 0:                                           ' "ペンカラーを設定する"
gG = 0
gB = 0
DO
  FN drawPoint:                                   ' "マウスによる描画"
  HANDLEEVENTS:                                   ' "イベント処理は自動(こりゃ楽ですな)"
UNTIL gQuit_flag
CALL DISPOSEGWORLD(gOffScreen&):                  ' "オフスクリーンの破棄(関数にしてもいいですね)"
END