■プロフィール

うなちょ

Author:うなちょ
うなちょのほんわか日記へようこそ

RagnarokOnlineと、PC版Minecraft++Forge+MODで
遊びつつ、片手間でWindowsのソフト作って遊んでます

■最近の記事
■最近のコメント
■最近のトラックバック

■月別アーカイブ
■カテゴリー

■リンク
■RSSフィード
■ブログ内検索

うなちょホットライン

うなちょへ直接連絡したい場合は、こちらから。
うなちょのPCメールへ着信するので、比較的
レスポンス早いかも…?(5分周期でチェック中)
※E-Mailですので、メルアドがある方のみです
※うなちょの携帯に転送する方法は、直接聞いて
  くだし。
無料アクセス解析
スポンサーサイト
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。


スポンサー広告 | --:--:--

MFCでGDI+のDrawImage()を使う方法 (ちょっとだけ詳細編)
さて、スマホ壁紙コンバーターで使ったDrawImageについて、メモの変わりに残してみよう。

コンバーターのコードは長いし他の機能もあるので、今回は検証用もかねて、DrawImageのテスト用ツールを作って検証してみた。




【手順1】GDI+の初期化
MFCでGdiplusを使うには、初期化をしてやらねばならない。

こんな感じ。


BOOL CDrawImageTestApp::InitInstance()
{
// アプリケーション マニフェストが visual スタイルを有効にするために、
// ComCtl32.dll バージョン 6 以降の使用を指定する場合は、
// Windows XP に InitCommonControls() が必要です。さもなければ、ウィンドウ作成はすべて失敗します。
InitCommonControls();

CWinApp::InitInstance();

AfxEnableControlContainer();

// 標準初期化
// これらの機能を使わずに、最終的な実行可能ファイルのサイズを縮小したい場合は、
// 以下から、不要な初期化ルーチンを
// 削除してください。
// 設定が格納されているレジストリ キーを変更します。
// TODO: この文字列を、会社名または組織名などの、
// 適切な文字列に変更してください。
//SetRegistryKey(_T("アプリケーション ウィザードで生成されたローカル アプリケーション"));
free( (void *)m_pszProfileName );
m_pszProfileName = _tcsdup( _T(".\\DrawImageTest.ini") );

//GDI+初期化コード
GdiplusStartupInput GSInput;
GdiplusStartupOutput GSOutput;
ULONG_PTR pulToken;
ULONG_PTR pulHookToken;
GSInput.SuppressBackgroundThread = TRUE;
GdiplusStartup( &pulToken, &GSInput, &GSOutput );
GSOutput.NotificationHook( &pulHookToken );

CDrawImageTestDlg dlg;
m_pMainWnd = &dlg;
INT_PTR nResponse = dlg.DoModal();
if (nResponse == IDOK)
{
// TODO: ダイアログが <OK> で消された時のコードを
// 記述してください。
}
else if (nResponse == IDCANCEL)
{
// TODO: ダイアログが <キャンセル> で消された時のコードを
// 記述してください。
}

//GDI+後始末コード
GSOutput.NotificationUnhook( pulHookToken );
GdiplusShutdown( pulToken );

// ダイアログは閉じられました。アプリケーションのメッセージ ポンプを開始しないで
// アプリケーションを終了するために FALSE を返してください。
return FALSE;
}


※コンパイラが古い(2003)ので注意!

基本的には、上記に示す通りStartupとShutdownを呼び出すだけ。

だが、ここで驚いたのがWin7の32Bitと64Bitでは、(32ビットアプリの)挙動が少々違う模様だ、ということ。

なんでかしらんが、コンバーターは上記の用なフックとかバックグラウンドスレッド停止(TRUE設定している所)とかやって無いんだけど、問題無く動くのに対して、この検証用ツールはエラーで動かなかった。

コンバーターはWin7 64Bitなんだけど、ツールはWin7 32Bit。てことは、WOW64の環境だとエラーにならないってことらしい。なんでだろうな…。

まぁ、動くからいいか。

これは他の人のBlogを参考に作ったのであるが、コンバーターではGdiplusStartup()の第3パラメータはNULL。


とりあえず、(このツールはダイアログアプリなので) DoModal()を挟んでおけばOK。




【手順2】DrawImageを使ってみよう

次、使う方法。

画像を変換するのは、1つのメッセージでやらせて、1行で変換できる様にした。



ヘッダ:stdafx.h
~途中省略~
#include <atlImage.h>
#include <gdiplus.h>
using namespace Gdiplus;
#pragma comment( lib, "gdiplus.lib" )

ヘッダ:CDrawImageTestDlg.h
#pragma once

#define WM_UPDATEDRAW (WM_USER+0x0001)

class CDrawImageTestDlg : public CDialog
{
~途中省略~
// 実装
protected:
~途中省略~
DECLARE_MESSAGE_MAP()
LRESULT OnUpdateDraw( WPARAM wp, LPARAM lp );

private:
CImage m_ImgBase;
CBitmap m_BmpBase; //m_ImgBaseと透過の画像を保持する
CDC m_DCBase;
CBitmap m_BmpDraw; //拡大縮小後の画像を保持する描画用画像
CDC m_DCDraw;
CSize m_szDraw;
int m_nDrawMode;
CString m_strDrawMode;
};

ソースコード:CDrawImageTestDlg.cpp

BEGIN_MESSAGE_MAP(CDrawImageTestDlg, CDialog)
~途中省略~
//}}AFX_MSG_MAP
ON_MESSAGE( WM_UPDATEDRAW, OnUpdateDraw )
END_MESSAGE_MAP()

//初期化ルーチン
BOOL CDrawImageTestDlg::OnInitDialog()
{
CDialog::OnInitDialog();

// このダイアログのアイコンを設定します。アプリケーションのメイン ウィンドウがダイアログでない場合、
// Framework は、この設定を自動的に行います。
SetIcon(m_hIcon, TRUE); // 大きいアイコンの設定
SetIcon(m_hIcon, FALSE); // 小さいアイコンの設定

// TODO: 初期化をここに追加します。
CString strFile = theApp.GetProfileString( "SETTING", "ImageFile", ".\\una.png" );
m_ImgBase.Load( strFile );

PostMessage( WM_UPDATEDRAW );

return TRUE; // フォーカスをコントロールに設定した場合を除き、TRUE を返します。
}

//拡大・縮小ルーチン
LRESULT CDrawImageTestDlg::OnUpdateDraw( WPARAM wp, LPARAM lp )
{
//各種画像エリアとペアのDCの削除
if( m_BmpBase.GetSafeHandle() != NULL ){
m_DCBase.DeleteDC();
m_BmpBase.DeleteObject();
}
if( m_BmpDraw.GetSafeHandle() != NULL ){
m_DCDraw.DeleteDC();
m_BmpDraw.DeleteObject();
}

//クライアント矩形取得
CRect rcClient;
GetClientRect( &rcClient );
m_szDraw.SetSize( rcClient.Width(), rcClient.Height() );

//画像エリア作成
CDC* pWndDC = GetDC();
m_BmpBase.CreateCompatibleBitmap( pWndDC, m_ImgBase.GetWidth(), m_ImgBase.GetHeight() );
m_DCBase.CreateCompatibleDC( pWndDC );
m_BmpDraw.CreateCompatibleBitmap( pWndDC, m_szDraw.cx, m_szDraw.cy );
m_DCDraw.CreateCompatibleDC( pWndDC );
ReleaseDC( pWndDC );
m_DCBase.SelectObject( &m_BmpBase ); //これを忘れると真っ黒画像!!!
m_DCDraw.SelectObject( &m_BmpDraw );

//Image->Bitmap
m_ImgBase.BitBlt( m_DCBase.GetSafeHdc(), 0, 0, SRCCOPY );

//Bitmapオブジェクトを、HBITMAPから生成
Bitmap* pBmp = Bitmap::FromHBITMAP( (HBITMAP )m_BmpBase.GetSafeHandle(), NULL );
//m_DCDrawを出力とするGraphicsオブジェクト作成
Graphics Grap( m_DCDraw.GetSafeHdc() );

//補完モード設定
switch( m_nDrawMode ){
default:
case 0: Grap.SetInterpolationMode( InterpolationModeNearestNeighbor );
m_strDrawMode = "Neighbor";
break;
case 1: Grap.SetInterpolationMode( InterpolationModeBilinear );
m_strDrawMode = "Bilinear";
break;
case 2: Grap.SetInterpolationMode( InterpolationModeHighQualityBilinear );
m_strDrawMode = "HighQualityBilinear";
break;
case 3: Grap.SetInterpolationMode( InterpolationModeBicubic );
m_strDrawMode = "BiCubic";
break;
case 4: Grap.SetInterpolationMode( InterpolationModeHighQualityBicubic );
m_strDrawMode = "HighQualityBicubic";
break;
}

//Bitmapオブジェクトの持つ画像を、Graphicsオブジェクトへ転送(クライアントの大きさに拡大・縮小)
Grap.DrawImage( pBmp, 0, 0, rcClient.Width(), rcClient.Height() );

//使わなくなったBitmapオブジェクトの削除
//Graphicsオブジェクトはスコープ外に行ったら消える(…かもしれない?)
delete pBmp;

//ウィンドウ再描画指示
Invalidate( FALSE );

return( 0 );
}


m_ImgBaseには、OnInitDialog()でLoad()を使って画像を読み込んである。これでBMPの他、Jpeg、PNG、GIF等も読める。

m_DCBase & m_BmpBaseは、m_ImgBase の画像をCDCとCBitmapで使うために確保してあるワークみたいなもの。

後で説明するが、正直HBITMAPが欲しいだけなので、別段メンバーで確保しておく必要はなかったかもしれない…

※ただし、m_ImgBaseからは取得できなかった。
 (方法があるかもしれないけど、少なくともGet~()という処理の呼び出しでは取得できなかった)


m_DCDraw & m_BmpDraw は、ウィンドウへの描画処理 (OnPaint()) へ拡大・縮小した画像を渡すためのバッファ。

こうやって、加工済画像を確保しておくのは、最近の私のお気に入り。

再拡大・縮小が必要の無い再描画指示ってあるわけで、その都度拡大・縮小したくないってだけ。

この処理は単純に拡大縮小しているだけだけど、画像に対して直接編集をかけるコードが大量にあるツール等を作ったら、再描画処理(OnPaint())に編集コードは書きたくは無い…!と考えているのですよ。

それに、途中どこで再描画が入るかわからんので、自分で再描画コードを割り込ませちゃっても大丈夫な様に、描画用とワークを分けてある。



結局、m_BmpBase、こいつは、DrawImage()へ渡すための等倍画像を保持している、と思えばOK。

なんでこんな回りくどい事をするかというと、DrawImage()へ渡すための画像は、Gdiplus::Image でなければならないから。

IStream経由でもできるけど、こいつは面倒なので、HBITMAPから持ってくる方法を探していたら…

・DrawImage()の元画像ってGdiplus::Imageのポインタか・・・

・GlobalAlloc()だのなんだの呼び出してIStreamに渡してImage作るのか~…めんどくs(ry

・IStreamにどこから格納するんだろ?BITMAPINFOHEADER入れんのか?・・・うん、わからん!

・HBITMAPから画像を持ってこれるのあるかな・・・(ヘッダ漁り)

・ほぉ、Gdiplus::Bitmapが持ってこれるか…FromHBITMAPはぐぐる先生に聞いたほうが早そうだ

・ん?Bitmapって、スーパークラスがImageなんだなー…

・もしかして、もしかして、派生してるBitmapが、指定できたりしないか…?(改造改造・・・)

・やった、Bitmapでも使えるぞ!!!

という流れ。だから、画像を一度HBITMAPに持ち直す必要があった。

CImageはHBITMAPなさそうだったので、簡単にBitBlt()で転送させることにした。

そのために、m_DCBaseが必要。まぁm_BmpBaseをいじくるには、DCあったほうが楽だしね。


再度ファイルを読み込む場合は、m_BmpBase.GetSafeHandle()がNULL以外の場合に限り、DeleteObject()してる。
(当然ペアになっているm_DCBaseも消します。なお、先にDCを消すのが良い・・・のかな?いちいちSelectObject()したくねーしなー)

まぁ、クリアしないで使う訳にもいきませんからね。

拡大・縮小処理(OnUpdateDraw()) をメッセージにしているのは、単純に描画指示とのぶつかりをある程度抑止したいからってだけで、これといった深い意味は無い。




【手順3】拡大・縮小の際の補完モードを変更するべ

Gdiplus::Graphicsには、拡大・縮小に使うロジック (補完モード) が設定できる。

ソース内でも指定しているが、Gdiplus::Graphics::SetInterpolationMode()という関数があり、こいつに以下のパラメータを渡すことで設定する。
 InterpolationModeNearestNeighbor
 InterpolationModeBilinear
 InterpolationModeHighQualityBilinear
 InterpolationModeBicubic
 InterpolationModeHighQualityBicubic

なお、こいつらはGdiplus名称空間にいるので、using namespace Gdiplus をつけていない場合は、きちんとGdiplus::を付けましょうね。

※おいらは、未だに名称空間なんてなんでできたんだろ?って思ってますけどね~。
 どうでもいいけど、namespaceはIntelliSense(2003のね)にでてこねぇ…マンドクセ


さて、前記OnUpdateDraw()の場合は、GraphicsをGrapという名前で使っているので、Grap.SetInterpolationMode(ほにゃらら)と呼び出せばOKです。

あ、DrawImage()の前に呼び出さないと意味ないよ?

※”ほにゃらら”がコンパイルエラーになった?知らんがな(´・ω・`)



なお、StretchBltは、NearestNeighborが使われています。(全タイプ試してみるとわかります。ただ、微妙に違いますけどね)

BilinearやBibubicにするだけで効果が見えるのですが、斜め線とかの場合、線が切れているのが見えてしまいます。

なんとか補完しているのを隠したいという場合は、HighQualityの方を指定すると幸せになれるかもしれません。

ただし、これは多少画像がぼやけるので注意です。一度お試ししてみるのをお勧めします。

※思ったよりがんばって補完してくれます。3~4倍拡大した線も、ドット境界が結構隠れてしまいます




【終わりだよっ!】

いずれにせよ、StretchBlt()を使うよりはきれいに拡大・縮小できるのではないかと思います。

いろいろ自分でロジックを組むのもよいのですが、どうせすでにOSがある程度できるものをもっていて、さらに使うまでそんなに苦労しないのですから、使わない手はないかと思います。

・・・まぁ、GDI+の土俵で動く関係上、ある程度環境依存になるのはしょうがないんでしょうね。


もしエラーがでる環境の場合、StrechBlt()で我慢してもらうという場合は、フラグで制御すればよろしいかと思います。

せめて、SetStretchBltMode( HALFTONE ) だの、同( COLORONCOLOR )位は付けてあげたいものです。


まぁ、ぐぐってもなかなかMFCでHBITMAPの画像を使うというものが見つけれなかったので書いてみました。
(もちろん参考にした所はあるので、皆無ではないですよ。ただ探すと.NETFrameworkのだったり、C#とかだしねぇ…?)

実際、スマホ壁紙コンバーターの場合は、一度ファイルに保存してそれを読み出すという荒業も使ってましたからねぇ。

gdiplusのヘッダを見てHBITMAPから持ってこれるのはわかったので、これは収穫あったなーという所。

※ただし、アプリのお仕事が終わりそうなので、仕事には使えない・・・かもしれない(´・ω・`)ショボーン



(*゚ω゚)ノ最近年とったので、メモしておかないと忘れるのだよっ!


┣【MFCなこと】 | 19:55:20 | Trackback(0) | Comments(0)

コメントの投稿

管理者にだけ表示を許可する

FC2Ad

上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。