ビットマップの作成について

[戻る]
Air 2004/11/04(木) 23:28:27
いつもお世話になってます、Air です。

現在、自分でビットマップを作成しようとしているのですが
わからないことが数点あり書き込みさせていただきました。

まず、BITMAPINFO 構造体についてです。
メンバである、BITMAPINFOHEADER 構造体の方はさほど問題ないのですが
もう片方の RGBQUAD 構造体の作成方法がよくわかりません。
というか、これは何に使われるものなのでしょうか?

そして、BITMAPINFO 構造体が出来た後
CreateDIBitmap() か CreateDIBSection() を使いビットマップを作成すると
思うのですが、この両者の違いがわからず、どちらを使えばよいか迷っています。
どういうときにどちがを使えば効果的なのでしょうか?

皆様、大変お忙しいところ恐縮ですが
ご教示よろしくお願い致します。

・開発環境
WindowsXP  VC++6.0 MFC

ゆう 2004/11/05(金) 00:49:41
>まず、BITMAPINFO 構造体についてです。
>メンバである、BITMAPINFOHEADER 構造体の方はさほど問題ないのですが
>もう片方の RGBQUAD 構造体の作成方法がよくわかりません。
>というか、これは何に使われるものなのでしょうか?

ビットマップは1ピクセルに必要なビット数が、biBitCountに設定されていますが、
8bit以下の場合は、パレットと呼ばれるRGBQUADの配列が必要になります。
そこに色情報を保持しておいて、実データの中身はそのパレットのインデックス番号で、
表します。

そのパレットを操作する方法として、BITMAPINFO構造体があります。
フルカラー(24bit)の場合はパレットはないので、BITMAPINFOは必要ありません。

BITMAPINFO構造体はちょっとインチキな構造体で、Auto変数の様に静的に
確保することができません。
動的にメモリを確保して、構造体の定義にはbmiColors[1]しかないのに、
bmiColors[0],bmiColors[1]...という本来ならオーバーランするような
使い方で、後に続くRGBQUADにアクセスします。
biBitCountが8なら最大256個、4なら16個、1なら2個のRGBQUADが並んぶ様にメモリを
確保する必要があります。
BITMAPINFO* pInfo = (BITMAPINFO*) new char[ sizeof(BITMAPINFOHEADER) +
sizeof(RGBQUAD) * ( 1 << biBitCount ) ] ;
みたいな感じです。

こんなところで解りますでしょうか。

>CreateDIBitmap() か CreateDIBSection() を使いビットマップを作成すると
>思うのですが、この両者の違いがわからず、どちらを使えばよいか迷っています。

CreateDIBitmap() はDDBです。
CreateDIBSection() はDIBです。
DIBの方がファイルに保存する時は便利ですが、表示する時はほんの少し処理が遅いです。
(DIB -DDB変換が行われる為です。今時のパソコンだとフルカラー表示が主流なので、
ほとんど差はありませんが、昔の256色しか表示できないパソコンだと、わりと時間が
かかりました。)

DDBにすると、ビットマップファイルの情報の一部がなくなってしまうので、
それをどこかにとっておかないと、保存する時にもとの物と変わってしまいます。
(biXPelsPerMeter、biYPelsPerMeterですからなくなっても困らないかもしれませんが)

まあどちらを使うかは好き好きでいいと思いますが、私なら、CreateDIBSectionを使います。

Air 2004/11/05(金) 09:01:11
さっそくのご返事ありがとうございます。

> ビットマップは1ピクセルに必要なビット数が、biBitCountに設定されていますが、
> 8bit以下の場合は、パレットと呼ばれるRGBQUADの配列が必要になります。
> そこに色情報を保持しておいて、実データの中身はそのパレットのインデックス番号
で、
> 表します。

> そのパレットを操作する方法として、BITMAPINFO構造体があります。
> フルカラー(24bit)の場合はパレットはないので、BITMAPINFOは必要ありません。

作成しようとしているものは24ビットフルカラーのものです。
その場合は必要ないとのことですが、CreateDIBitmap() CreateDIBSection() 共に
引数で BITMAPINFO が必要になります。その場合、RGBQUAD にはどのような値を
入れておけばよろしいのでしょうか?
ちなみに、両関数共、引数に DIB_PAL_COLORS か DIB_RGB_COLORS を指定する部分が
ありますが、パレットが無い場合は DIB_PAL_COLORS ということになるんでしょうか?
勘違いしてたらすいません…

> CreateDIBitmap() はDDBです。
> CreateDIBSection() はDIBです。
> DIBの方がファイルに保存する時は便利ですが、表示する時はほんの少し処理が遅いで
す。
> (DIB -DDB変換が行われる為です。今時のパソコンだとフルカラー表示が主流なので、
> ほとんど差はありませんが、昔の256色しか表示できないパソコンだと、わりと時間が
> かかりました。)

なるほど。
やはり処理は早いにこしたことはないので、CreateDIBSection を使ってみようと
思います。( といっても、まだよくわかってないのですが… )

ご迷惑おかけして申し訳ありません。
再度、アドバイスをよろしくお願いします。

PATIO 2004/11/05(金) 10:42:05
Win32APIはC言語で書かれていますのでC言語の考え方でいくと納得しやすいと思います。
C言語でメモリを扱う場合に構造体のポインタを使って操作すると言うのは常套手段です
。
例えば、GlobalAlloc等で確保したメモリアドレスを構造体のポインタに入れて
構造体として操作するなんて事は日常茶飯事です。
この場合、BITMAPINFO構造体は正にこういう使い方をするための構造体で、
BITMAPINFOの最後にあるbmiColorsメンバーはパレット等(必ずしもパレットとは限りま
せん)の情報にアクセスする為の足がかりに使うために配列として宣言されています。
実際に使う場合は、ポインタ扱いで使う事になりますけれど、
ポインタで宣言してしまうとBITMAPINFOHEADERの後ろにくっつくと言う構造が表現できな
くなるので便宜上こうなっているのだと思います。

24bitカラーの場合、bmiColorsの部分のサイズは事実上0になりますから
結果的にはBITMAPINFO構造体とBITMAPINFOHEADER構造体の中身は同じになります。
C言語では他のポインタに読み替えて使用するというのはよくある話なので
覚えておいた方がいいです。
但し、他のポインタに読み替える場合、読み替えた後の動作がうまく行く事は読み替える
側で補償しなくてはなりませんので注意が必要です。

PATIO 2004/11/05(金) 10:46:05
この辺はC言語的な使用法になりますが、Windowsでプログラミングする以上は
Win32APIを全く使わないというわけには行きませんからC言語の知識もないと
躓く事が出てくると思います。
この辺は地道な自己学習が必要って事なんだと思います。

Air 2004/11/05(金) 11:21:08
ご返答ありがとうございます。

とりあえず、CreateDIBSection で BITMAPINFOHEADER を BITMAPINFO にキャストして
やってみました。
そして、CreateDIBSection() の4つ目の引数(VOID *ppvBits) のポインタに
素直に memcpy() で ビットマップ実データをコピーして
BitBlt() で 表示してみたのですが、画像がきれいに180度回転してくれています。
これはなぜなのでしょうか?実データを作るときにそれを考慮しろという
ことなのでしょうか?
いまいちわからん!と突っ込まれる前にソースの書かせていただきます。

_h = 0;
for(_i=0 ; _i<m_tga_head.height ; _i++)
{
  for(_j=0 ; _j<m_tga_head.width ; _j++, _h++)
  {
    p_bmp[ _h*4+2 ] = ( p_tex[ _h*4+0 ] * p_tex[ _h*4+3 ] / 0xff );
p_bmp[ _h*4+1 ] = ( p_tex[ _h*4+1 ] * p_tex[ _h*4+3 ] / 0xff );
                p_bmp[ _h*4+0 ] = ( p_tex[ _h*4+2 ] * p_tex
[ _h*4+3 ] / 0xff );
//                p_bmp[ _h*4+3 ] = 0;
            }
        }

こう 2004/11/05(金) 11:30:48
DIBとGDIは座標系が上下逆と聞いたことがあります。
それが関係してるのではないでしょうか?

こちらは参考になりませんか?
http://www.sm.rim.or.jp/%7Eshishido/
Windowsプログラミング研究室で、DIBについて詳しく書かれています。

Air 2004/11/05(金) 11:32:07
すいません!
ソースを書いてるときに手がすべってしまいました!
改めて書かせていただきます。( TABキーのせいです… )

// ビットマップ実データ (わかりやすく黒→白のグラデーション)
_h = 0;
for(_i=0 ; _i<m_tga_head.height ; _i++)
{
  for(_j=0 ; _j<m_tga_head.width ; _j++, _h++)
  {
    p_bmp[ _h*4+2 ] = _i*2;
    p_bmp[ _h*4+1 ] = _i*2;
        p_bmp[ _h*4+0 ] = _i*2;
    p_bmp[ _h*4+3 ] = 0;
    }
}

// 表示部分
{
  HDC      hDC;
  HDC      hCompatiDC;
  CWnd*      p_pic;
  HBITMAP myDIB;
  HBITMAP oldDIB;
  void*      pBitsDib;
  HWND      hWnd = p_frame->m_hWnd;
  PAINTSTRUCT ps;
        
  p_pic = (CWnd *)GetDlgItem(IDC_PICTURE);
  hDC = p_pic->GetDC()->GetSafeHdc();
        
  ::BeginPaint(hWnd, &ps);
        
  hCompatiDC = CreateCompatibleDC(hDC);
        
  myDIB = CreateDIBSection( (HDC)0, (BITMAPINFO *)(&p_tex->m_bi),       
          DIB_RGB_COLORS, (void **)&pBitsDib, NULL, 0 );
    memcpy( pBitsDib, p_tex->m_pBmp, 128 * 128 * 4 );
        
    oldDIB = (HBITMAP)SelectObject(hCompatiDC, myDIB);
        
    BitBlt(hDC, 0, 0, 128, 128, hCompatiDC, 0, 0, SRCCOPY );
        
    ::EndPaint(hWnd, &ps);
        
    SelectObject( hCompatiDC, oldDIB );
    DeleteObject( myDIB );
    DeleteObject(hCompatiDC);
}

となっています。
抜き出して編集したので、若干おかしいところがあるかもしれませんが
こんな感じです。
さらに白状してしまうと、この表示部分はどこから呼ぶのが
一番いいのかと迷ってもいます。

不細工なミスをして申し訳ありません。
よろしくお願い致します。

瀬戸っぷ [E-Mail] 2004/11/05(金) 11:34:33
> BitBlt() で 表示してみたのですが、画像がきれいに180度回転してくれています。
> これはなぜなのでしょうか?実データを作るときにそれを考慮しろという
> ことなのでしょうか?

180度回転(本来左上にあるモノが右下にある)と上下反転(左上のモノが左下に)とどちら
でしょうか?
ビットマップの場合、スキャンラインが左下からだったハズなので、
データをそのように用意する必要があったかと思われますが。

# DIBとかあまりいぢらないので後は詳しい方にお願いします。
# BITMAPINFOHEADER構造体のbiHeightの解説に書かれているかと。
# サクっとググってイキのページが参考になりますか?
# http://hp.vector.co.jp/authors/VA023539/tips/bitmap/001.htm

## とか書いているウチにこうかさんからレスが。

Air 2004/11/05(金) 11:42:26
大変素早いレス、ありがとうございます。
ただ、こうさんのレスタイミングだけは泣きそうです…

自分で調べていたときは、このようなページは見つけれませんでした。
今からじっくり読んでみます。

そして、大事な事を書き忘れてました。
このビットマップはピクチャボックスに書こうとしています。
(今更すいません…)

描画場所(タイミング)などについても、さらなるアドバイスをいただけると
大変ありがたいです。

よろしくお願い致します。

こう 2004/11/05(金) 12:21:01
>この表示部分はどこから呼ぶのが一番いいのかと迷ってもいます。
コントロール(IDC_PICTURE)をサブクラス化して
WM_PAINTがきたときに描くといいんじゃないかなぁ。

試したことないので、うまくいくかは判りませんが……(^^;

Air 2004/11/05(金) 14:42:48
ご返答ありがとうございます。

180度回転する件ですが、BITMAPINFOHEADER を作成する際
biHeight の値をマイナス掛けすればうまくいきました。
( って、こんな修正でいいのでしょうかとやや不安 )

描画に関してですが、他にポリゴン等を描画しているのですが、そちらは
マルチメディアタイマーを使って1/60秒で 基本ビュークラスの
OnDraw()の中で行っています。
そこにピクチャボックスの描画をはさむのはさすがになぁ…と思います。

ピクチャボックス再描画が必要になる場合として、
・ウィンドウサイズ変更
・ウィンドウの最小化からの復帰
・ダイアログバーにピクチャボックスを配置し、そのバーをON/OFFしているので
 新たにONリクエストがかかったとき

ぐらいだと思われますが、基本ビュークラスにOnPaint()を追加し
その中で描画処理を入れれば、全てに対応できるでしょうか?

あつかましい質問で恐縮ですが
ご教示の程、よろしくお願いします。

Air 2004/11/06(土) 11:58:12
前述のピクチャボックスの描画に関してですが。

とりあえず、ウィンドウのサイズ変更と最小化からの復帰に対応させようと
メインのフレームに OnPaint() を追加し、そこで描画処理を入れてみました。

すると、サイズ変更時、最小化からの復帰時共に、一瞬は表示されるのですが
最終的に画像が消えてしまいます。
どのようにすればうまく再描画できるでしょうか?

よろしくお願い致します。

ゆう 2004/11/06(土) 15:58:57
>描画に関してですが、他にポリゴン等を描画しているのですが、そちらは
>マルチメディアタイマーを使って1/60秒で 基本ビュークラスの
>OnDraw()の中で行っています。
>そこにピクチャボックスの描画をはさむのはさすがになぁ…と思います。

HBITMAPやコンパチなHDCをメンバ変数にしておいて、OnDrawではBitBlt転送のみ
にすればそんなに負担にならないと思います。

あと余計な事かもしれませんが、MFCなら普通はCBitmap、CDCにAttatchします。
デストラクタで破棄してくれるので、後始末が楽です。
コンストラクタでBeginPaintを呼んで、デストラクタでEndPaintを呼ぶ
CPaintDCというクラスもあります。

ゆう 2004/11/06(土) 16:07:29
良く考えてみると、BeginPaint、EndPaintはWM_PAINTのみでしか使えない関数だった様な
気がします。GetDC、ReleaseDCを使ってください。

シャノン 2004/11/06(土) 21:40:16
> 180度回転する件ですが、BITMAPINFOHEADER を作成する際
> biHeight の値をマイナス掛けすればうまくいきました。
> ( って、こんな修正でいいのでしょうかとやや不安 )

そんな修正でよかったりします。
biHeight が正の値の場合はボトムアップですが、負の値の場合はトップダウンとなりま
す。
ただし、場合によるとトップダウンビットマップを解釈しないビューワがあるかもしれ
ませんので、より確実を期するならボトムアップがいいでしょう。
biHeight を負にするだけでも、間違いではありません。

なお、biWidth を負にしても左右反転にはなりませんので注意。

瀬戸っぷ [E-Mail] 2004/11/06(土) 22:53:10
いちお〜補足ですが…
「上下反転」であって「180度回転」ではないです。
意味が変わりますので注意された方がよろしいかと。

Air 2004/11/07(日) 00:51:46
ご返答ありがとうございます。

ゆうさん >
とりあえず、言われたとおり、HBITMAP や hDC 等をメンバ変数にし
ビットマップ作成は画像を読み込んだ時のみ行い
BitBlt() は毎割り込み行う方法でやってみようと思います。
結果はまた報告させていただきます。

シャノンさん、瀬戸っぷさん >
なるほど。少し不安だったので安心しました。
新たな忠告も肝に命じておきます。

ちょっと表題からははずれるのですが、
現在ピクチャボックスを 160 x 136 の大きさで配置しています。
これはダイアログエディタ上のサイズです。
そこに 128 x 128 のビットマップを表示してみたのですが
かなり小さいというか左右などはちょうど半分ぐらいに収まっています。
ダイアログ座標というものを考えても予想以上に小さかったので
ん??と思いました。
このサイズについての違いといいますか、その辺りを少し詳しく
教えていただければと思います。

よろしくお願い致します。

シャノン 2004/11/07(日) 02:16:30
ダイアログ座標での何ポイントがピクセル単位での何ポイントに相当するかは
ダイアログのフォントの大きさに依存しますので、フォントがデカければデカいほど
ダイアログ座標では同じ値でも、ピクセル単位では差が大きくなりますが。

ちなみに、デザイン時にダイアログ座標をいくつにすればピクセル単位で望みの
値になるのかはわかりません。
ご存知でしたらぜひ教えていただけないでしょうか。

Air 2004/11/07(日) 03:13:24
ご返答ありがとうございます。

シャノンさん >
なるほど。なんとなく納得しました。
自分なりに色々調整してみようと思います。

ピクチャボックスへの描画の件ですが、例のメンバ変数を用意し
BitBlt() だけ毎割り込み行う方法で、うまくいってるっぽいです。
ありがとうございました。

ここでささいなしつもんなのですが、

HDC とか HBITMAP は最後に DeleteObject なり ReleaseDC を行うと思います。
私の場合は

// ビットマップ作成部分より抜粋
p_pic = (CWnd *)GetDlgItem(IDC_PICTURE);
hDC = p_pic->GetDC()->GetSafeHdc();
        
hCompatiDC = CreateCompatibleDC(hDC);
        
myDIB = CreateDIBSection( (HDC)0, (BITMAPINFO *)(&p_tex->m_bi),       
         DIB_RGB_COLORS, (void **)&pBitsDib, NULL, 0 );

-------------------------------------------------------------------
// 解放部分        
DeleteObject( myDIB );
DeleteObject(hCompatiDC);
ReleaseDC(hWnd, hDC);

こういう感じなのですが (かなり省略して書いていますが)
仮にこの hDC, hCompatiDC, myDIB が一度も使われないまま
解放処理(削除処理)を行った場合というのは大丈夫なのでしょうか?

素人丸出しの質問で恐縮ですが
よろしくお願いします。

瀬戸っぷ [E-Mail] 2004/11/07(日) 11:22:18
> ちなみに、デザイン時にダイアログ座標をいくつにすればピクセル単位で望みの
> 値になるのかはわかりません。
> ご存知でしたらぜひ教えていただけないでしょうか。

ピクセル単位で処理したことはありませんが…

とりあえず、下記のページ辺りが参考になるでしょうか?(Delphiのページだったりしますが)
http://homepage2.nifty.com/Mr_XRAY/Halbow/Chap23.html
「ダイアログ単位」ってヤツですね。
変換式が書かれているので参考になるかと。

> こういう感じなのですが (かなり省略して書いていますが)
> 仮にこの hDC, hCompatiDC, myDIB が一度も使われないまま
> 解放処理(削除処理)を行った場合というのは大丈夫なのでしょうか?

作成と解放をドコに記述したかによるのではないでしょうか?
MFCは詳しくないのでドコに書くのが適切なのか判りませんが。
(コンストラクタとデストラクタ? OnInitDialogとOnDestory?)

Air 2004/11/07(日) 12:59:57
> 作成と解放をドコに記述したかによるのではないでしょうか?
> MFCは詳しくないのでドコに書くのが適切なのか判りませんが。
> (コンストラクタとデストラクタ? OnInitDialogとOnDestory?)

変数自体は、基本となるビュークラスのメンバとして宣言しています。
で、画像の読み込みが一度でも行われれば使われるといった感じです。
解放は同じビュークラスのデストラクタで行っています。

なので、画像を一度も読み込まずにアプリを終了すれば
一度も使ってはいないけど解放ということになります。

シャノン 2004/11/07(日) 17:44:57
ダイアログ単位について語るスレではないでしょうから、それに関してはこの辺で打ち
止めにして、あとは別スレを立てるなりした方がよいかと。

参考までに、以前この掲示板でダイアログ単位について質問したスレ。
http://rararahp.cool.ne.jp/cgi-bin/lng/vc/vclng.cgi?print+200312/03120064.txt

問題点としては、
・GetDialogBaseUnits で取得した値は、ダイアログのフォントが「System」だと
 想定している
・ダイアログのプロパティで「Use System Font」を TRUE にしても、フォントは
 「System」ではなく「MS Shell Dlg」になってしまう。
の2点。

で、今やって見た所、わりとすんなりできたので変換関数を掲載。

// フォントを考慮した GetDialogBaseUnits 改良版
void CHogeDlg::GetDialogBaseUnits2( LONG & baseUnitX, LONG & baseUnitY )
{
  DWORD dwStyle = GetStyle();
  if( ( dwStyle & ( DS_SETFONT | DS_SHELLFONT ) ) == 0 )
  {
    DWORD dwBaseUnits = GetDialogBaseUnits();
    baseUnitX = LOWORD( dwBaseUnits );
    baseUnitY = HIWORD( dwBaseUnits );
  }
  else
  {
    CDC * pDC = GetDC();

    CDC dc;
    dc.CreateCompatibleDC( pDC );

    CFont * pOldFont = dc.SelectObject( GetFont() );

    TEXTMETRIC metric;
    dc.GetTextMetrics( &metric );
    baseUnitX = metric.tmAveCharWidth;
    baseUnitY = metric.tmHeight;

    dc.SelectObject( pOldFont );
    dc.DeleteDC();

    ReleaseDC( pDC );
  }
}

// MapDialogRect に相当する関数
void CDLUDlg::DialogUnitToPixel( RECT & rect )
{
  LONG baseUnitX = 0;
  LONG baseUnitY = 0;
  GetDialogBaseUnits2( baseUnitX, baseUnitY );

  rect.left = MulDiv( rect.left, baseUnitX, 4 );
  rect.top = MulDiv( rect.top, baseUnitY, 8 );
  rect.right = MulDiv( rect.right, baseUnitX, 4 );
  rect.bottom = MulDiv( rect.bottom, baseUnitY, 8 );
}

// MapDialogRect の逆変換(ピクセル→ダイアログ単位)を行う関数
void CDLUDlg::PixelToDialogUnit( RECT & rect )
{
  LONG baseUnitX = 0;
  LONG baseUnitY = 0;
  GetDialogBaseUnits2( baseUnitX, baseUnitY );

  rect.left = MulDiv( rect.left, 4, baseUnitX );
  rect.top = MulDiv( rect.top, 8, baseUnitY );
  rect.right = MulDiv( rect.right, 4, baseUnitX );
  rect.bottom = MulDiv( rect.bottom, 8, baseUnitY );
}

シャノン 2004/11/07(日) 18:05:19
ごめん。この関数じゃうまく行かない場合あり。原因究明中。

Air 2004/11/07(日) 22:13:20
[[解決]]
表題とかなり内容が変わってきておりますので、一旦解決とし
別スレを立てさせていただこうと思います。

ちなみに、ピクチャボックスのサイズに関しては
以前シャノンさんの立てられたスレにありました
MapDialogRect() を使い、何とかうまくいきそうです。

だらだらと引っ張ってすいませんでした。
ご返答くださった皆様、ありがとうございました。

PATIO 2004/11/08(月) 10:33:39
既に解決してしまっているようですが、一つだけアドバイスを。

ビットマップのラインスキャンの方向を反転するために高さをマイナス値にする
件ですけれど、
仕様上は正しいのですが、ドライバによってはこの辺の解釈が怪しい物もあるようです。
グラフィックボードのドライバ多分大丈夫だと思うのですが、
プリンタドライバに関しては結構手抜きをしている物もあるみたいで
高さをマイナスにするよりも自分で逆にたどるようにした方がトラブルが少ないです。
描画に関してはドライバが絡むので苦労させられます。
特に印刷周りはグラフィックドライバよりも手抜きをしていたり、
独自の実装をしていたりで苦しめられる事が多いのでなるべくプログラム側で
対処してあわせてしまう方が無難です。

半分愚痴ですが、ご参考になれば。

古いスレッドには追加発言できません。