SDL版「お尻たたき」ソフト第一回

 SDLによるフレームバッファ表示テストでは、SDLを使うと簡単にフレームバッファを扱えることがわかりました。今度は、フレームバッファ以外にもマウスイベントや透過描画機能、タイマ、サウンドなどSDLの機能を一通り試すプログラムを作ってSDLの基本的な使い方を一つのプログラムにまとめてみようと思います。プログラムの題材は、これらの機能を一通り備えた「真赤なお尻」にしましょう。このプログラムは、ビットマップのお尻を表示してマウスが移動したら移動先に手のビットマップを透過描画、さらにお尻がクリックされたらクリックされた部分を赤くする、という処理を行います。すでにWindwos版Java版を公開していますが、Java版を元に開発してみることにしました。

 今回は、まずお尻と手のビットマップを読み込んでサーフェスを作成し、マウスの移動に応じて手のサーフェスを透過描画する処理までを作ってみます。今回のプログラムの作成を通して、サーフェスの透過描画処理の性能やイベント処理の基本的な流れを確かめることが出来るでしょう。

BMPファイルの読み込みとサーフェス作成

 まず、お尻と手の画像はSDLによるフレームバッファ表示テストと同様に32ビットピクセル列のデータとして作成します。お尻と手の画像は24ビットフルカラーBMPファイルに格納されているので、画像の大きさに応じたバッファを確保してそこにBMPファイルに格納されている画像を32ビットのピクセル列データとして読み込む必要があります。この処理は、JavaによるBMP読み込みと同様の方法でピクセル列を32ビット化すれば良いでしょう。流れとしては、

  1. ファイルの大きさを調べ、読み込みバッファを確保
  2. 読み込みバッファにファイル全体を読み込む
  3. 画像の大きさを調べ、ピクセル列のバッファを確保
  4. ピクセル列バッファに読み込みバッファ内の画像を32ビット化して格納

 という感じになります。この処理を

  int loadBMP(char *fn,Uint32 **pixels);

 という関数にまとめ、fnで指定されたBMPの画像を32ビット化してバッファに読み込み、そのポインタを*pixelsに格納するようにしましょう。なお、この関数は返り値として読み込んだピクセルの数を返します。

 まず、ファイルの大きさについては、「ファイルの末尾にシークしてその位置を取得する」という方法で調べることにします。

  FILE *fp=fopen(fn,"rb");

  fseek(fp,0,SEEK_END); /* ファイルの末尾にシーク */
  size=ftell(fp)+1;     /* 現在の位置を取得 */
  rewind(fp);           /* ファイル先頭にまき戻す */

 これでsizeにファイルの大きさが求まったので、読み込みバッファbufを確保し、そこにファイル全体を読み込みます。続いて、読み込んだバッファからバッファの先頭から画像までのオフセット、画像の大きさを取得を取得し、ピクセル列用バッファを確保。また、BMP内画像データの横一列の大きさも計算しておきます。

  offset=*((int *)(buf+10));
  width=*((int *)(buf+18));
  height=*((int *)(buf+22));

  *pixels=(Uint32 *)malloc(width*height*4); /* ピクセル列用バッファ確保 */

  if (width % 4==0) /* BMP内画像の横一列の大きさ計算 */
      length=width*3;
  else
      length=width*3+(4-(width*3)%4);

 あとはbuf+offsetから格納されている画像データを32ビット化しながらピクセル列用バッファに格納していくだけ。

  for (i=0;i<height;i++)
      for (j=0;j<width;j++) {

          p=j*3+(height-i-1)*length+offset;
          (*pixels)[j+i*width]=buf[p]+buf[p+1]*256+buf[p+2]*65536;

      }

 以上で「ファイル名とポインタを指定すると、指定されたファイルの画像データを指定されたポインタが指すバッファに読み込む」関数loadBMP()が出来ました。この関数を使ってまずお尻のピクセル列(buttocks)と手のピクセル列(hand)を作成しましょう。

  loadBMP("osiri.bmp",&buttocks);
  loadBMP("hand.bmp",&hand);

 続いて、バックバッファ用フレームバッファbackbufとサーフェスback、それから手のサーフェスteを作成します。手のサーフェスには、透過色として0x00ffffffを指定し、この色の部分を除いて描画されるようにしましょう。

  backbuf=(Uint32 *)malloc(450*300*4);
  back=SDL_CreateRGBSurfaceFrom((void *)backbuf,450,300,32,450*4,
        0x00ff0000,0x0000ff00,0x000000ff,0);

  te=SDL_CreateRGBSurfaceFrom((void *)hand,160,90,32,160*4,
        0x00ff0000,0x0000ff00,0x000000ff,0);

  SDL_SetColorKey(te,SDL_SRCCOLORKEY,0x00ffffff); /* 透過色指定 */

 実際の描画処理では、まずお尻のピクセル列buttocksをバックバッファのフレームバッファにコピーし、その上に手のサーフェスを描画します。手を描く位置は変数hx、hyで指定し、この変数はマウスの移動イベントの時に更新するようにしましょう。実際にバックバッファに描画し、それを画面上のサーフェスwindowに表示する関数draw()は以下のようになります。

  void draw() {

      memcpy(backbuf,buttocks,450*300*4); /* バックバッファにお尻をコピー */

      SDL_BlitSurface(te,&rectTe,back,&rectHand); /* 手を透過描画 */

      SDL_BlitSurface(back,&rectBack,window,&rectFore);

      SDL_UpdateRect(window,0,0,0,0);

  }

 以上でお尻と手のサーフェスを作成し、それを画面上に表示する仕組みが出来ました。

マウス移動と手の描画

 次に、マウスの移動に応じて手を移動させる処理です。SDLのサーフェス描画では、描画元・描画先双方の描画領域をSDLRect構造体で指定します。この構造体には左上座標と大きさを指定するので、手のサーフェス用のSDLRect構造体の座標を変更してdraw()を呼び出せば新しい手の位置で全体が再描画されますね。初期状態では、手のサーフェスの領域を表すrectHandと手の描画位置を表すrectTeを以下のように設定しました。手の描画位置を変更する場合は、rectTex, yを変更するわけです。

  rectHand.x=0;
  rectHand.y=0;
  rectHand.w=160;
  rectHand.h=90;

  rectTe=rectHand;

 マウスカーソルの移動はSDL_MOUSEMOTIONイベントで検知します。今回もプログラムのメインループでSDL_WaitEvent()によるメッセージループを作成していますから、このループにSDL_MOUSEMOTIONイベントが来たら、マウスカーソルの座標をイベント構造体から取得して手の描画位置を決める変数hy, hyを更新しましょう。さらに、SDL_MOUSEMOTIONイベントの処理では、マウスカーソルが表示用サーフェスの領域にあったらSDL_ShowCursor()でマウスカーソルを消す処理も入れておきます。

  case SDL_MOUSEMOTION:

      hx=e.motion.x;
      hy=e.motion.y;

      rectHand.x=hx-80;
      rectHand.y=hy-45;
      draw();

      if (hx<rectFore.x || hx>=rectFore.x+rectFore.w ||
          hy<rectFore.y || hy>=rectFore.y+rectFore.h)
          SDL_ShowCursor(SDL_ENABLE);
      else
          SDL_ShowCursor(SDL_DISABLE);

      break;

 これで、今回の「お尻と手のビットマップを読み込んでサーフェスを作成し、マウスの移動に応じて手のサーフェスを透過描画する」部分の処理は完成です。

プログラム

 プログラムを実行すると、お尻と手が表示されます。マウスを動かすと、それに追随して手も動くので動きを確認してみましょう。

 今回のアーカイブには、Makefileを同梱しています。Linuxでは、アーカイブを適当なディレクトリに展開しmakeするとosiriという実行ファイルが出来るので、./osiriで実行してください。

プログラムダウンロード(sdosiri1.zip/97KB)

戻る