OnPaint()でビットマップを表示

[戻る]
ガラ 2011/02/05(土) 18:49:13
VC++ 6.0 MFC 
CStaticを派生させたクラスをつくって、そのクラスのOnPaint()
でビットマップを表示させようとしているのですが、
どうも、メモリ上にわざわざ、デバイスコンテキストを作るのが無駄をしているような
気がします。
LoadBitmap()で読んだものをそのまま描画用デバイスコンテキストに出力できないでし
ょうか?
void CStaticBitmapDisp::OnPaint() 
{
    CPaintDC dc(this); // 描画用のデバイス コンテキスト

    CBitmap Bitmap ;
    Bitmap.LoadBitmap(m_FileName) ;
//    if (!this->SetBitmap(Bitmap)) {
//        AfxMessageBox("SetBitmap() Error");
//    }
    
    DeleteObject(Bitmap) ;
}
*m_FileName(LPCTSTR)は、ビットマップのファイル名です。

ガラ 2011/02/05(土) 18:50:19
追記 OS XPです。

しま 2011/02/06(日) 01:36:36
>LoadBitmap()で読んだものをそのまま描画用デバイスコンテキストに出力
>できないでしょうか?

VC++6 の MFC でビットマップを扱うのは SDK を用いるのと大差ありません
なので、DDBとDIBとDIBSection とについて知っておく必要があります
  DDBには色々と制約があるのであまりお薦めしません

DDBは DC(Device Context) に選択して使います
DIBは DC に選択することができませんので別の操作 StretchDIBits() で描画します
DIBSection は DDB,DIB 両方の扱いができるものです

CBitmap で LoadBitmap() する以上は ComapatibleDC に選択する必要があるでしょう
ただ、::LoadImage() を用いるのなら DIBSection として読込めるので OK かも
知れません

ガラ 2011/02/06(日) 08:04:31
しまさん、レスありがとうございます。

マニュアルには、以下のことしか書いていませんので、DIBについて調べているとこ
ろです。

DDB(device-dependent bitmap:デバイス依存ビットマップ)
  特殊なプリンタ、ディスプレイのときだけに使われるビット配列

DIB(device-independent bitmap:非デバイス依存ビットマップ)
  ビットマップの高さ・幅を指定する、イメージがクリエイトされるデバイスのカラ
ーフォーマット、イメージがクリエイトされるデバイスのレゾリュ−ションの構造体で
構成されるビット配列

ガラ 2011/02/06(日) 09:11:18
こんな感じかな?って思うんです。
.bmpは、DBIで、描画用デバイスコンテキストは、DDB。
これを変換して使うためにメモリデバイスコンテキストをつかう?
ってことになるんじゃないかな?と思うんですが・・
描画用デバイスコンテキストは、DBIにはならない?

しま 2011/02/06(日) 13:12:11
データー構造ではなく、DDB,DIB,DIBSection の使い方の違いについて
知って欲しいと思います

確かに、DDB <->DIB の相互変換ができるようにはなっていますが、単に描画する
場合は相互変換しないでそのまま使うことが多いと思います

>>どうも、メモリ上にわざわざ、デバイスコンテキストを作るのが無駄をしている
>>ような気がします。

ということへの返事を書いているつもりです
文面から、DCにビットマップを選択するのを避けたいとお考えだと判断したので
それなら DIB,DIBSection を用いれば Blit()(or StretchBlit()) ではなく
描画対象のDCに StretchDIBits() で出力できるのではないかと思ったということです

ryo 2011/02/07(月) 02:07:29
ちょいと話はずれますが、

OnPaintのたびに、
ファイルを読み込み・解放と、デバイスコンテキストを作成・解放
をするほうが面倒ではないでしょうか?

ガラ 2011/02/07(月) 08:35:15
しまさん、ryoさん、レスありがとうございます。
すいませんが、今、DIB/DDBなど、あれこれ読んでいますので、レスに時間がか
かります。

hirocco 2011/02/07(月) 09:25:55
>CBitmap Bitmap ;
>Bitmap.LoadBitmap(m_FileName) ;
LoadBitmapの引数m_FileNameって
ファイルの名称とかパスになってませんか?

hirocco 2011/02/07(月) 09:50:00
LoadBitmapでファイルパスは指定できないし、
SetBitmapならWM_PAINTでの処理は無駄だと思います
CStaticの派生クラスのスタイルにはちゃんとSS_BITMAPが指定されていますか?
そしてサブクラス化も不要に思えます

サブクラス化が不要/必要問わず、どのようにしてインスタンスを作りますか?
DDX?SubclassDlgItem?Create?

ガラ 2011/02/07(月) 10:52:24
hiroccoさん、レスありがとうございます。

>LoadBitmapの引数m_FileNameって
>ファイルの名称とかパスになってませんか?

なってました。リソースにビットマップファイルを挿入して、リソースIDに変えまし
た。

○ちょっと手がかりを入手
システムが 256 色表示の場合、リソースに登録されている 256 色のビットマップ
(DIB ビットマップ)を LoadBitmap 関数で読み込むと、予約されている 20 色の
システムカラーで描画されてしまいます。これは LoadBitmap 関数が DIB ビットマップ
を DDB ビットマップに変換する際に
カラー情報が失われるためです。

ただいま、256色のビットマップを表示しようとしていますが、 LoadBitmap()ならD
IBのリソースをDDBに変換してしまう。ということですね。
DDBを使うとGDI関数が使えないということですが・・・そのへんがどうもわから
ないところです。

ガラ 2011/02/07(月) 11:52:20
>スタイルにはちゃんとSS_BITMAPが指定されていますか?
スタイル指定は、していません。デフォルトのままです。

>サブクラス化が不要/必要問わず、どのようにしてインスタンスを作りますか?
メンバの一つとして、インスタンスを作ります。
SubclassDlgItemということになるとおもいます。

仲澤@失業者 2011/02/07(月) 12:43:01
>どうも、メモリ上にわざわざ、デバイスコンテキストを作るのが無駄をしているような
>気がします。
>LoadBitmap()で読んだものをそのまま描画用デバイスコンテキストに出力できないでし
>ょうか?

できません。

デバイスコンテキスト(HDC)は作成時のデフォルト状態で1×1のモノクロ
ビットマップ(BMP)を選択しています。ただし、BeginPaint()又はGetDC()
で取得したHDC(CPaintDCも同じ)は画面上の当該HWNDのクライアント領域全体を指
し示すBMPを選択しています。つまりHDCとBMPは一蓮托生なのです。
たとえばBitBlt()関数は対象HDC同士の転送を意味しますが、実態はそれぞれの
HDCが選択しているBMP同士のBitの転送を行います。HDCに直接BMPを表示させる
ことはできません。当然、システムが割り当てたHDC(=PaintDC、ClientDC等)が
選択しているBMPを別のBMPに選択しなおすことも危なくてできません。

次に、VC6で作成するリソース内のBMPは全て16(ないしシステム20色)色の
BMPに変換されて保持されてしまいます。これは、リソースをコンパイルする
ときに変換されるため、元となったBMPが256色以上の場合、いかなる方法を
もってしても、元の色に戻すことはできません。リソース内に元の状態で
ビットマップファイル全体を保持するにはカスタムリソースという手法を
使う方法もありますが、一般には、リソースではなく、外部ファイルとして
別にロードする方法を使用します。

最後に、単に表示するだけならば、デバイス依存のビットマップ(=BMP)と
デバイス独立系ビットマップ(=DIB、DIBセクション)の違いは、ほとんど
ありません。大きな違いは、BMPの場合、画像データ部分を分離して単なる
データ配列として扱えない点です。BMPの画像部分に対して、このようなことを
行いたい場合は、一旦DIB又はDIBセクションに変換する必要があります。

上記の様に、また、しまさんの発言どおり、BMP、DIB、DIBセクションの
性質の違い、および、それに依存するそれぞれの取り扱い方法の違いを
理解していたほうが、より適切に利用できると考えられます。
できれば、HDCとの関係についても知っておくとベターです。

いしだ 2011/02/07(月) 13:17:58
CImageを使えば、メモリビットマップを作らずに
DCに直接描画できますが、これってVC6には無かったでしたっけ?

CImage m_image;
m_image.LoadFromResource(AfxGetInstanceHandle(), IDB_BITMAP1);

CPaintDC dc(this);
m_image.Draw(dc, 0, 0);

hirocco 2011/02/07(月) 13:42:57
>CStaticを派生させたクラスをつくって、そのクラスのOnPaint()
>でビットマップを表示させようとしているのですが、
>どうも、メモリ上にわざわざ、デバイスコンテキストを作るのが
>無駄をしているような気がします。
>LoadBitmap()で読んだものをそのまま描画用デバイスコンテキストに
>出力できないでしょうか?
知識ばかり先行しているようにお見受けします
 派生させる意味
 デバイスコンテキストの意味
 LoadBitmapの振る舞い
 DIBやDDBの差異

>SubclassDlgItemということになるとおもいます
おもいます?

えっと, でも, 要するにBitmapをファイルで読み込んで"STATIC"の背景に設定したいっ
ていうことであって,
そこにWM_PAINTやデバイスコンテキスト/DIB/DDB, ましてやLoadBitmapまでも関係なく
最終的にファイルと同じビットマップを表示したいって事じゃないかなぁと…
どうでしょう?

それだと仮定すると
表示させたい"STATIC"のスタイルはSS_BITMAPが指定されているべき
LoadBitmapではなくLoadImageであるべき
初期化時に背景を設定するためにSetBitmapを使用するべき
以上です

生成済みの"STATIC"コントロールに
ModifyStyle( 0x000F,SS_BITMAP );
を実行させて, 
(HBITMAP)LoadImage( NULL,"ファイルのフルパ
ス",IMAGE_BITMAP,0,0,LR_DEFAULTSIZE|LR_CREATEDIBSECTION|LR_LOADFROMFILE );

LoadImageで作られたHBITMAPをSetBitmapです
あとは何も要りませんし, 考える必要も無し, インスタンスも気にしないで大丈夫でし
ょう
ただ, LoadImageで作られたHBITMAPはメンバ変数として取っておいて, 終了時に解放し
ます

VC6.0ってよく知りませんけど, LoadImageが無いとか, そういうことあります?

どかな?


あ, どうしてもリソースからしかもVC6.0から元々のビット深さのビットマップを操作し
たいとか
そういうことだったのかな?
VC.NETからしかしりませんのでお役に立てません...
ゴメンナサイ

ガラ 2011/02/07(月) 13:51:37
仲澤@失業者さんレスありがとうございます。
StretchDIBits()をしらべていますが、
int StretchDIBits(
   :
  CONST VOID *lpBits,            // address of bitmap bits
   :
   :
);

lpBits・・・DIB の色データが入ったバイト配列へのポインタを指定します。

という引数があります。外部ファイルをイメージしていると、これをread()などで
ヘッダーなどから順次読んでいく必要があるようですね。

ガラ 2011/02/07(月) 14:26:02
いしださん、レスありがとうございます
試してみましたが、コンパイルエラーが出力されました。
protected:
    CImage m_image ;
error C2146: 構文エラー : ';' が、識別子 'm_image' の前に必要です。
error C2501: 'CImage' : 識別名を宣言するのに、型が指定されていません。

いしだ 2011/02/07(月) 16:24:32
> 試してみましたが、コンパイルエラーが出力されました

VC6の頃には無いクラスだったようです。失礼しました。

ちなみに、リソース内のビットマップ画像をそのまま表示するのであれば、
イメージリストにしてしまうという方法を自分はたまに使います。
基本的には複数の画像をまとめて扱うものですが、
DCに直接描画できますし、透過処理も可能です。

CImageList m_imageList;

CBitmap bitmap;
bitmap.LoadBitmap(IDB_BITMAP1);
m_imageList.Create(100, 100, ILC_COLOR24 | ILC_MASK, 0, 1);
m_imageList.Add(&bitmap, RGB(255, 0, 255));

CPaintDC dc(this);
m_imageList.Draw(&dc, 0, CPoint(50, 50), ILD_TRANSPARENT);

PATIO 2011/02/07(月) 16:48:14
前のスレッドを読んでいない人には流れが分からないような気がします。
前のスレッドの内容で今回の質問に必要な前提があるのなら、手短に説明を
行なってから質問にはいるか、前のスレッドにリンクを張るべきでしょう。
(個人的にはリンクで済ませてしまうとスレッド全体に目を通さなくては
ならなくなるので質問する方で要約して説明すべきだと思ってます)

派生させたクラスを作成して対応する必要があるのは、
読み込んだ画像の上にGDIで描画して表示したいからだったと思いますが、
その部分は変わっていないのですよね?
であれば、その部分の説明が無いと話が通りません。
仮に特定の画像をCStaticに表示させたいだけの話なら
自分で描画するまでも無く、仲澤さん、hiroccoさんが書かれている
通りの話になります。その方が無駄にコードを書く必要が有りません。
CStaticの機能を使って表示させた方が簡単ですし、確実です。

LordBitmapで読み込んだCBitmapを画面に表示する為に
一旦、画面とコンパチブルなDCを作成した上でそこに選択状態にして
画面のDCに転送するのはDDBを画面のデバイスに適合させて
それを画面に転送するという手続き上必要な操作なので無駄では有りません。
この部分はなぜそうする必要があるのかと言う部分まで学ぶ事ができれば、
ベストですが、理解しきれない場合は一先ず手続きとして納得するしか
ありません。但し、毎回DCを作成してビットマップをロードしてを
繰り返すのは非効率なので効率の良い実装を考える必要はあると思います。
しなくても良い処理をせずに済ませられるようにするのは当然必要です。

いろいろな情報が入ってきているのでご自身で一度整理された方が良いと
思います。その上で自分がやりたい事にあった方法を選ぶ必要があると思います。

ガラ 2011/02/07(月) 17:15:54
hirocco さん、レスありがとうございます。
LoadImageはあります。
::LoadImage()からStretchDIBits() につなげれるのでは?
という予想を立てています。
現在、調査中です。

ガラ 2011/02/07(月) 17:35:07
いしださん、レスありがとうございます。
以下のようにコーディングして、表示されました。
メンバ変数の宣言
protected:
    CImageList m_imageList;

ソースファイル
int CXXXXX::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
    if (CDialog::OnCreate(lpCreateStruct) == -1)
        return -1;
    
    // TODO: この位置に固有の作成用コードを追加してください

    CBitmap bitmap;
    bitmap.LoadBitmap(IDB_BITMAP1);
    m_imageList.Create(100, 100, ILC_COLOR24 | ILC_MASK, 0, 1);
    m_imageList.Add(&bitmap, RGB(255, 0, 255));
    bitmap.DeleteObject() ;
    
    return 0;
}
void CXXXXX::OnPaint() 
{
    CPaintDC dc(this); // 描画用のデバイス コンテキスト
    
    // TODO: この位置にメッセージ ハンドラ用のコードを追加してください
    m_imageList.Draw(&dc, 0, CPoint(50, 50), ILD_TRANSPARENT);

    // 描画用メッセージとして CDialog::OnPaint() を呼び出してはいけません
}

ガラ 2011/02/07(月) 17:54:39
[[解決]]
PATIO さん、レスありがとうございます。
前レスからの流れをくみますが、今回は、
”ビットマップを画面に表示するだけなのに、もうすこし、簡単にできないのかな?”
という疑問がありましたので、UPしてみました。
いろいろ、みなさんからご意見をいただき、メモリデバイスコンテキストをとって、
そこに、書き込んで、W_PAINTが送られるたびにBitBlt()で再表示するのが、無駄ではな
いということがわかりました。
また、CBitmapは、デバイスコンテキストにsetobjectすることで、デバイスコンテキス
トのフォーマットを決め、データを書き込むということも・・
いろいろ勉強になりました。
みなさん、ありがとうございました。

ヘッダファイル(クラス宣言)での記述
protected:
    CDC        m_mDC ;                
    CBitmap     m_Bitmap ;
    BITMAP        m_BitmapStruct ;
    CBitmap     *m_pPrevBitmap ;

ソースファイル
int CDerivationOfCDialog::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
    if (CDialog::OnCreate(lpCreateStruct) == -1)
        return -1;
    
    // TODO: この位置に固有の作成用コードを追加してください
    CDC *dc = this->GetDC(); 
    ASSERT(m_Bitmap.LoadBitmap(IDB_CURIELOGO)) ;
    ASSERT(m_mDC.CreateCompatibleDC(dc)) ;//メモリデバイスコンテキストを作
成
    ASSERT(m_pPrevBitmap = m_mDC.SelectObject(&m_Bitmap)) ;
    ASSERT(m_Bitmap.GetBitmap(&m_BitmapStruct)) ;

    this->ReleaseDC(dc) ;
    
    return 0;
}

void CDerivationOfCDialog::OnPaint() 
{
    CPaintDC dc(this); // 描画用のデバイス コンテキスト
    
    dc.BitBlt(0, 0, m_BitmapStruct.bmWidthBytes, m_BitmapStruct.bmHeight, 
&m_mDC, 0, 0, SRCCOPY) ;
    // 描画用メッセージとして CDialog::OnPaint() を呼び出してはいけません
}

void CDerivationOfCDialog::OnDestroy() 
{
    CDialog::OnDestroy();
    
    // TODO: この位置にメッセージ ハンドラ用のコードを追加してください
    ASSERT(m_mDC.SelectObject(m_pPrevBitmap)) ;
    ASSERT(m_Bitmap.DeleteObject()) ;
    ASSERT(m_mDC.DeleteDC()) ;

}

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