DIBのアニメーション表示

最近、「動画」が話題になっています。
DVDにビデオキャプチャ、テレビ電話、さらに高速なMPEG−2のソフトエンコードを可能にする(?)Pentium3....。CPUやメモリ、記憶装置などの発達で手軽に大量のデータを処理できるようになって、個人レベルの動画処理も現実的なものになってきたようです(ビデオキャプチャボード、欲しいなあ。ついでにMOも)。

動画、というのは結局は少しずつ違う画像(フレーム)を連続表示しているだけなのですが、一秒間に24枚とか30枚とか表示するんでデータ量がとてつもなく大きくなるんですよね。例えば、256×256のフルカラー画像は1枚64KB×3=192KB、これを30枚分用意するとそれだけで192KB×30=5MB以上になります。そこで、いろいろな圧縮技術が開発されているわけですが、このあたりは「動き」を利用したり人間の目の性質なども考慮されていて結構面白そうな分野です(難しいけど)。

今回は、この「動画」に挑戦してみます。と言っても、とりあえず圧縮などは考えずビットマップを連続表示するプログラムを作ってみましょう。データ量は大きくなりますが、その分最後まで素直なDIBのままなので、コマ数を変えたりフレームにいろいろな「いたずら」をして遊んでみてください。

フレームの構造とアニメーション

今回の動画プログラムは、メモリ内のフレームデータを順に表示していくだけのものです。フレームは、128×128/256色のDIBにしました。これだと一枚16KBですね。このフレームを128枚用意して、毎秒20フレームずつ表示します。

各フレームのデータは、ポインタ配列lpFrame[128]で先頭アドレスを指定できるようにします。こうしておけば、そのアドレスとDIBヘッダをStretchDIBitsに渡せばそのフレームを描画する事が出来るので、別に表示用のダブルバッファを用意する必要もありません。後は、適当な間隔で表示するフレーム番号を管理する変数dwIndexを更新して再描画すれば良いでしょう。

  lpBuf=(LPBYTE)GlobalAlloc /* 必要なメモリをまとめて確保 */
    (GPTR,sizeof(BITMAPINFO)+255*sizeof(RGBQUAD)+128*128*128);

今回は、まず必要なメモリをまとめて確保します。必要になるのは、DIBヘッダとカラーテーブル、それにフレームデータ用のメモリですね。メモリを確保したら、DIBヘッダとカラーテーブルの領域に分けてそれぞれポインタを設定します。

  /* DIBヘッダ先頭アドレス */
  lpDIB=(LPBITMAPINFO)lpBuf;

  /* カラーテーブル先頭アドレス */
  lpRGB=(RGBQUAD*)(lpBuf+sizeof(BITMAPINFOHEADER));

フレームデータは、カラーテーブルの後に置く事にするとその先頭アドレスは

 lpRGB+256*sizeof(RGBQUAD);

です。そして、1フレームの大きさは128×128バイトでしたから、iフレーム目の先頭アドレスは以下の式で求められる事になります。

 lpFrame[i]=(LPBYTE)lpRGB+256*sizeof(RGBQUAD)+128*128*i;

こうしてフレームデータ用の領域が出来たら、適当なフレームを作ってみましょう。今回のプログラムでは、四角や直線が動き回ったり画面がフラッシュするデータを作ってみました。

表示する時には、表示周期より短いメインループで再生時間を監視しながらフレームを描画します。これは、一定時間が経ったらフレームを描画しなおすよりも、毎回描画(ウインドウを再描画)するようにした方が良いでしょう。つまり、時間が経ったら描画するのではなく、常に描画し続け時間に応じて描画するフレームを変えて行くのです。

・メインループ内の関数で描画するフレームを指定

  void draw(void) { /* メインループ描画処理 */

      if (GetTickCount()>dwTime+5)
          dwTime=GetTickCount();
      else /* 前回の処理から5ms 以上経ってなければ戻る */
          return;

      if (dwStart==0) /* 最初の時間を記録 */
          dwStart=GetTickCount();

      /* 描画するフレームの番号を1/20秒毎に1増やす */
      dwIndex=(GetTickCount()-dwStart)/50;

      if (dwIndex>127) { /* 最後まで再生したらまた最初から */

          dwIndex=127;
          dwStart=0;

      }

      InvalidateRect(hwMain,NULL,FALSE);
      UpdateWindow (hwMain);             /* フレーム描画 */

  }

・描画時に指定されたフレームを描画

  case WM_PAINT: /* WM_PAINTメッセージ処理 */

      hdc=BeginPaint(hwnd,&ps);

      /* フレームを表示 */
      StretchDIBits(hdc,8,8,128,128,0,0,128,128,
        lpFrame[dwIndex],lpDIB,DIB_RGB_COLORS,SRCCOPY);

      EndPaint(hwnd,&ps);

      break;

再生開始時に、時間をGetTickCount(1/1000秒単位の時刻取得)でdwStartに記憶しておけば、再生するフレーム番号は以下のようになります(50×20=1000ミリ秒=1秒)。

  /* 描画するフレームの番号を1/20秒毎に1増やす */
  dwIndex=(GetTickCount()-dwStart)/50;

プログラム

プログラムソース表示

実行すると128×128ピクセルのアニメが表示されます。フレームデータをいじったり、コマ数やメインループの周期を変えるとどうなるか、試してみましょう。


プログラミング資料庫 > DIB/Waveによる音声画像処理実験室