ウィンドウのビットマップ保存(範囲選択し、縮小してさらにモノクロ変換)

[戻る]
トト 2009/12/10(木) 11:28:04
CScrollViewに表示している画像をビットマップに保存したいと思っております。

現在は、GDI+のImage:Saveで行っているのですが、思うように縮小が出来ません。
(Saveを行うためにMetafileにRectを渡すのですが、そこに100×100を渡すと、ビット
マップの座標が0からという理由だと思うのですが、101pixelのビットマップが出来てし
まいます。)

そのため、GDIの関数ではなく、
http://www.sm.rim.or.jp/~shishido/wincap.html

この辺を参考にさせていただきながら、ビットマップを生成してみようと思っておりま
す。

そこで、タイトルにも記載させていただいております
・範囲選択
・縮小
・モノクロ(BiCoountが1)
という処理はどのように行うべきでしょうか?

大体こんな流れで・・・という回答だけでも構いませんのでご教授お願いいたします。
ちなみに環境はWindowsXP(SP2)、VC++2005です。

言語の知識も正直初心者レベルです。

以上、よろしくお願いいたします。

仲澤@失業者 2009/12/10(木) 13:12:10
ステップバイステップでやってみたらどうでしょう。
1.ウインドウをBMPにして保管してみて確認
2.上記を保管する前にStretchBlt()した結果を保管してみて確認
3.上記を保管する前にモノクロBMPを選択したDCにBitBltして保管

みい 2009/12/10(木) 13:16:04
この手のは一つずつ進めた方がよいと思うので、とりあえず縮小まで。
・範囲選択
  BitBltの引数で指定します
・縮小
  BitBlt→StretchBltに変更します
まずはBitBlt/StretchBltのヘルプを読んでみて下さい。

トト 2009/12/10(木) 13:55:27
さっそくのご回答ありがとうございますm(__)m

やはり全てをいきなりってのは無理ありますもんね・・・。
とりあえずビットマップのファイル保存を行ってみます・・・。
サンプルとかもしご存知でしたらご教授願います。

自分でも色々サンプルを探してみたんですが、「WINAPI WinMain」とか「LRESULT 
CALLBACK WndProc 」とか使ってるものしか見当たらず(参考にして自分で作れと言われ
ればそれまでなんですが・・・)。

仲澤@失業者 2009/12/10(木) 14:18:16
MFCを使ってよいならCImageが使えます。
SDKだけならBITMAPFILEHEADERから理解しなければなりませんし、
慣れてないと手間をくいます。

トト 2009/12/10(木) 14:57:24
仲澤@失業者さん

本当に無知ですいませんが、
作成したいのは、CScrollViewに表示している画像のビットマップ保存です。

「MFCが使える」というのは、CScrollViewに表示している画像をCImageの関数などを使
用して縮小や保存処理が出来る、という事でしょうか?

(GDI+もCImageのsaveを使用しているので、出来れば保存処理はCImageではない方法で
行いたいです。)

意味分からない文章ですいません・・・。

仲澤@失業者 2009/12/10(木) 15:15:23
中途半端な回答になっている理由は「画像のビットマップ」なるものが、
何であるのかトトさんの発言から読み取れないからです。
そのオブジェクトが保管できるかどうかは、何に「表示」しているかとは、
無関係ですよねぇ。はっきり言って「CScrollViewに表示している」
かどうかなんて、関係ないんです。

逆に、もしCScrollViewのクライアント領域を保管したいのなら、
ビットマップを選択しているDCを与えて、全描画関数を
コールするだけで、クライアント領域のBMPが取得でます。

その後の処理は本レスの一連の最初に戻るわけです。

トト 2009/12/10(木) 15:28:22
画面に表示しているのはCADファイルです。(GDS2というストリームファイル)
そのファイルを読み込んで画面に描画しています。

>MFCを使ってよいならCImageが使えます。
という回答をいただいておりますが、質問に

>現在は、GDI+のImage:Saveで行っているのですが、思うように縮小が出来ません。
と記載させていただいております通り、CImageのSave関数ではビットマップ保存は出来
ても、思うように縮小が出来ない状態です。

>逆に、もしCScrollViewのクライアント領域を保管したいのなら、
>ビットマップを選択しているDCを与えて、全描画関数を
>コールするだけで、クライアント領域のBMPが取得でます。
この方法が記載されているホームページ等をご存知でしたら教えてくださいm(__)m

bun 2009/12/10(木) 17:25:08
以下の話の意味が分からないです。
>(Saveを行うためにMetafileにRectを渡すのですが、
> そこに100×100を渡すと、ビットマップの座標が0から
> という理由だと思うのですが、101pixelのビットマップ
> が出来てしまいます。)
もう少し、詳しく教えていただけますか?

そこが伝わらないと、トトさんの期待する回答は出てこない気がします。

トト 2009/12/10(木) 17:37:15
    Metafile(
        IN HDC                 referenceHdc,
        IN const Rect &        frameRect,
        IN MetafileFrameUnit   frameUnit   = MetafileFrameUnitGdi,
        IN EmfType             type        = EmfTypeEmfPlusDual,
        IN const WCHAR *       description = NULL
        )
GDI+のメタファイルです。

このRectに図面の始点座標と幅・高さを格納します。
このとき、100×200pixelのビットマップを生成したくて
RectのWidthに100、Heightに200を格納しても、生成されるビットマップは101×201にな
ってしまうということです。

言葉が足りないでしょうか?

ちなみにソースはフリーのものです。
http://www.vector.co.jp/soft/winnt/business/se243021.html

bun 2009/12/10(木) 18:37:34
いまいち理解できていないかもしれませんが、
ビットマップを保存する際のサイズがおかしいと言うだけなら、
Image:Save()の問題ではない気がします。

Metafile を介しているせいで、どこかでサイズが化けたのではないでしょうか?

Metafileを介さず、ビットマップを直接 Save()してしまうことをお勧めします。
CreateCompatibleBitmap()か何かしたビットマップがいますよね?
そのビットマップハンドルを、
  Bitmap::Bitmap(HBITMAP, HPALETTE)
コンストラクタに渡して、
  Image::Save()
を呼んでみてはどうでしょう?(BitmapはImageの派生クラス)

私はその方法でサイズ化けせずに保存できています。

仲澤@失業者 2009/12/10(木) 19:10:48
>このとき、100×200pixelのビットマップを生成したくて
>RectのWidthに100、Heightに200を格納しても、生成されるビットマップは101×201に>ってしまうということです。

おそらく、DCのグラフィックスモードが GM_ADVANCED になってるのでは
ないでしょうか。このモードでは矩形(RECT)の右端と、下端を「含む」ので、
そういうこともありえます。あんまり悩む必要もないかもしれません。

ビギナー 2009/12/10(木) 19:35:54
bunさん
>CreateCompatibleBitmap()か何かしたビットマップがいますよね?
>そのビットマップハンドルを、
>  Bitmap::Bitmap(HBITMAP, HPALETTE)
>コンストラクタに渡して、
>  Image::Save()
>を呼んでみてはどうでしょう?(BitmapはImageの派生クラス)

>私はその方法でサイズ化けせずに保存できています。
→CreateCompatibleBitmapしたビットマップはありますが
 それをどうすれば良いのか分かりません・・・・
 もし可能でしたら実際にソースコードで示していただけませんでしょうか?


仲澤@失業者さん

>おそらく、DCのグラフィックスモードが GM_ADVANCED になってるのでは
>ないでしょうか。このモードでは矩形(RECT)の右端と、下端を「含む」ので、
>そういうこともありえます。あんまり悩む必要もないかもしれません。
→SetGraphicsMode関数では「GM_COMPATIBLE」となっておりました。

bun 2009/12/10(木) 20:13:34
CreateCompatibleBitmapしたビットマップがあるなら、
そのまま、
 Bitmap::Bitmap(HBITMAP, HPALETTE)
に渡すだけですよ。

CBitmapはHBITMAPに自動変換されますので、
第一引数にそのまま渡すだけです。
第2引数はNULLでOKです。

そして、Save()を呼び出すだけ。
そこはすでにやってるみたいですから解説不要ですよね?

ビギナー 2009/12/10(木) 20:46:00
bunさん

えっと・・・

hBMP=CreateCompatibleBitmap(hdc,dwWidth,dwHeight);

で生成したビットマップ「hBMP」があります。

Bitmap::Bitmap(hBMP, NULL);
でいいんでしょうか???
それとも
CBitmap bmp = ::Bitmap(hBMP,NULL);

どういう風に記載するのか分かりません・・・orz
お助けを・・・

bun 2009/12/10(木) 21:08:23
こんな感じかな。

CLSID EncorderClsId;
EncoderParameters EncoderParams; 
  :
Bitmap Bmp(hBMP, NULL);
Bmp.Save([保存先パス], &EncorderClsId, &EncoderParams);

PATIO 2009/12/11(金) 13:40:16
もしかしてC++の文法に関しても知識が足りない?

MFC(CScrollViewとかはMFCのクラスです)を使っているのなら
少なくともC++言語の文法に関する知識は必須ですよ。
文法を知らずにC++のプログラムは書けません。
コピペで組み立てることはできるかもしれませんが、
それでは実際に行われている処理が理解出来ませんし、
メンテナンスが出来ないソースになってしまいます。

PATIO 2009/12/11(金) 13:44:59
事情は分かりませんけれど、
もしC++言語の文法自体を良く知らないのであれば、
一度きちんとC++言語の文法を勉強した方が良いです。
C++言語は文法とクラスの概念に関する知識が必要になるので
この辺が分かっていないとMFCを使いこなす事は難しいです。

できれば、MFCを使わない状況でまずC++言語の勉強を
純粋にした方が良いです。逆にC++言語の知識があれば、
MFCがやっている事もある程度理解出来ますし、
使い方も理解しやすくなります。

トト 2009/12/11(金) 14:38:15
PATIOさん

そうです。知識は乏しいです・・・。
でも勉強する時間が無いので、とりあえず動くモノを、と思っております。

【質問】
試行錯誤しながらコーディングしている最中なんですが、
StretchBltを使用して、縮小しようと思っております。

試しに、ピクチャーコントロールを2つ作成し、1つめに表示している画像を2つめの
ピクチャーコントロールに縮小・拡大して表示させることは出来ました。

この時、コピー元のDCとコピー先DCは異なったものでなければならないと思うのです
が、「CClientDC dc(this);」で取得したDCを縮小したいといった場合、どのように処理
するものなのでしょうか?

「ダミーのデバイスコンテキストハンドルを生成してそこにコピーする」
といった方法は可能なのでしょうか?
また、可能な場合は単純に
HDC hDC2;
のようにインスタンスだけ生成すれば良いのでしょうか?

みい 2009/12/11(金) 16:53:42
「CreateCompatibleDC」を使って仮想描画領域を作ります。

CDC dcBuffer;
CBitmap bmpBuffer;
CRect rcRect;

CClientDC dc(this);  //コピー元
// rcRectにコピー先の領域をセット
...
//

// 仮想描画領域
dcBuffer.CreateCompatibleDC(&dc);
bmpBuffer.CreateCompatibleBitmap(&dc, rcRect.Width(), rcRect.Height());
dcBuffer.SelectObject(&bmpBuffer);

// 処理
...

// 使い終わったら削除
dcBuffer.DeleteDC();
bmpBuffer.DeleteObject();

PATIO 2009/12/11(金) 17:02:10
HDC hdc2;としてもデバイスコンテキストのインスタンスにはなりません。
この辺の話はやっぱりちゃんと理解していないと厳しいような気もしますけれど。
HDCはあくまでもデバイスコンテキストを特定する為のハンドルに過ぎません。
ハンドル自体がデバイスコンテキストの実体(インスタンス)ではないと言う事は
理解されていますか?

ビットマップを転送したり、画面から複写する為に
メモリ上にコンパチブルなデバイスコンテキストを作成して
そこに画像を受け取る為のbitmapを選択しておくと言う方法は
C++に限らずWindowsではポピュラーな方法なので
メモリ上のDCに選択したbitmapに貼り付ける時に縮小すれば、
いいはずです。ただし、GDIのStretchBltの縮小は縮小できれば良い
というレベルなので縮小された画像の出来に関してはある程度あきらめるしか
ないかなと思います。
知識がないながらも動くものをと言うのはわかるのですが、
C++言語の知識がどの程度足りないのかも結構重要ですよ。
クラスの概念等殆ど知りませんと言う話なら、
このまま続けるとドハマリする可能性も否定できません。
実際の話、C++言語の知識に関しては最低限必要な物なので。
その上でWindowsの仕組みとかMFCのクラスの使い方とかの
知識が必要になるわけで、これらの一番土台になる部分なので
継ぎ接ぎでプログラミングをするとしてもかなり困難なのではないかなぁ。
と思うのです。
せめてC++言語の勉強だけでもされた方が良いと思います。

gak 2009/12/11(金) 17:40:28
> 「CClientDC dc(this);」
CClientDC だと今其の時画面に見えているイメージ(CScrollViewをスクロールして見え
るようになる部分は無理)しか取得できないよ。(そういう仕様なら問題は無いけど)

> とりあえず動くモノを
GDI+を使わないサンプル。自動改行入ってボロボロになるだろうけどソース載せる。

void CScrollView::****() {
    // イメージのCRect(100, 100, 200, 200)を白黒2色、CSize(300, 300)で保存
    SaveImage(CRect(100, 100, 200, 200), 1, 300, 300, _T("output.bmp"));
}
void CScrollView::SaveImage(const CRect& rect, int bitdepth, int cx, int cy,
LPCTSTR output) {
    const int colors = ((1 << bitdepth) & 0x1ff);
    const int headerLen = sizeof(BITMAPINFOHEADER) + sizeof(RGBQUAD) * colors;
    BITMAPINFO* bi = reinterpret_cast<BITMAPINFO*>(new BYTE[headerLen]);
    memset(bi, 0, sizeof(BITMAPINFOHEADER));
    bi->bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
    bi->bmiHeader.biWidth = cx;
    bi->bmiHeader.biHeight = cy;
    bi->bmiHeader.biPlanes = 1;
    bi->bmiHeader.biBitCount = bitdepth;
    bi->bmiHeader.biSizeImage = (((bi->bmiHeader.biBitCount *
bi->bmiHeader.biWidth + 31) / 32) * 4) * bi->bmiHeader.biHeight;
    // grayscaleパレット構築
    for (int i=0; i < colors; ++i) {
        bi->bmiColors[i].rgbBlue = bi->bmiColors[i].rgbGreen =
bi->bmiColors[i].rgbRed = ::MulDiv(255, i, (colors - 1));
        bi->bmiColors[i].rgbReserved = 0;
    }
    CDC dc;
    dc.Attach(::CreateCompatibleDC(NULL));
    void* bits;
    CBitmap bitmap;
    bitmap.Attach(::CreateDIBSection(NULL, bi, DIB_RGB_COLORS, &bits, NULL, 0));
    CBitmap* oldbmp = dc.SelectObject(&bitmap);
    dc.FillSolidRect(0, 0, bi->bmiHeader.biWidth, bi->bmiHeader.biHeight,
RGB(0xff, 0xff, 0xff));
    dc.SetMapMode(MM_ANISOTROPIC);
    dc.SetWindowOrg(0, 0);
    dc.SetViewportOrg(::MulDiv(-rect.left, cx, rect.Width()),
(::MulDiv(-rect.top, cy, rect.Height())));
    dc.SetWindowExt(rect.Size());
    dc.SetViewportExt(cx, cy);
    dc.SetStretchBltMode(HALFTONE);
    CScrollView::OnDraw(&dc); // イメージ描画処理
    dc.SelectObject(oldbmp);
    // bmp保存
    BITMAPFILEHEADER bfh = { 0 };
    bfh.bfType = 0x4d42;
    bfh.bfSize = sizeof(BITMAPFILEHEADER) + headerLen + bi->bmiHeader.biSizeImage;
    bfh.bfOffBits = sizeof(BITMAPFILEHEADER) + headerLen;
    CFile file(output, CFile::modeCreate | CFile::modeWrite);
    file.Write(&bfh, sizeof(BITMAPFILEHEADER));
    file.Write(bi, headerLen);
    file.Write(bits, bi->bmiHeader.biSizeImage);
    delete []reinterpret_cast<BYTE*>(bi);
}

問題点
・減色をシステムに丸投げしている(パレットから近似色取ってるだけ)為質が良くな
い。ディザも掛からない
・CScrollView::OnDraw() 内でマッピングモード関連を弄っている場合は調整が要る

ryo 2009/12/11(金) 18:02:03
時間がないなら、CImageが一番楽。

1:保存したいサイズのCImageをつくる(100*100など)
#このときモノクロでつくれば、モノクロ化も一発で可能(*1)

2:表示しているDCから、CImageへ、StrecthBltする。

3:CImageを保存。(CImageの破棄)

おわり

*1:ある色は白、別の色は黒・・・といった変換をいろいろ設定したいのなら
この方法ではできない

トト 2009/12/18(金) 12:27:19
gakさんに記載していただいているソースで試してみたのですが、
真っ白なビットマップが出力されてしまいます・・・。

これからryoさんに提案いただいた方法で試させていただきます。

トト 2009/12/28(月) 20:17:48
gakさんに記載していただいているソースについて質問させてください。

1.CreateDIBSectionの第一引数は「dc」でなくても良い?

2.biSizeImageの計算で、Y方向はバイト換算しなくても良い?

デバイスコンテキストとビットマップの結びつけが出来ていないのか、デバイスコンテ
キストに描画して出力しても何も表示されません・・・。

PATIO 2009/12/29(火) 11:15:51
HDCにNULLを指定すると多分デスクトップウインドウのハンドル扱いになったと思います。

あと、X方向のバウンダリ合わせの事を言っているのなら既にX方向でバウンダリはあって
いるのでY方向は必要ないです。メモリ上にどのように配置されるのかを考えるとその辺
はわかると思います。

PATIO 2009/12/29(火) 11:24:06
あと、ちょっと確認を。

ファイルに出力した結果を画像表示ソフトで表示しても何も表示されない
と言う事で良いのですか?
ファイルに出力する時は、DCに選択されたままの状態にしているとうまく行かないと思い
ます。
gakさんのソースでは出力前に非選択状態にしてますよ。

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