#analog
#norelated
#contents
* Today プラグインサンプルプログラム3 [#ya388a19]

[[Today プラグインサンプルプログラム2]]では、背景を透過して、文字を書いてみました。~
文字が表示できたら、今度は画像を表示してみたくなるのが人情というもの。~

というわけで、ただ表示するだけでは面白くないので、α(アルファ/透明度)の情報を持ったPNG画像を、背景上にBlendして表示してみましょう。~

具体的には、以下のような画像を用意し、数字を背景上に描画します。~

#ref(digital.png,left,nowrap,デジタル数字)

絵じゃなくて字じゃん!というツッコミはなしでw~
わかりにくいかもしれませんが、背景が透過しており、なおかつ数字の縁にはアンチエイリアスがかかっています。~
白一色だと見にくいと思うので、同じサイズの黒の画像を裏に重ねて、少し右下にずらして影の効果を出しています。~

** ファイルパス作成 [#b53c2144]
まずは、読み込む画像ファイルのフルパスを作る必要があります。~
WindowsMobileは、相対パスが使えないという制限もありますし、そうでなかったとしても、フルパス指定の方が、何かと安全です。~

今回は、DLLと同じところに画像ファイルを置きます。~
なのでまず、DLLがアタッチされた時に、自分自身のパスを保存しておきます。~
現状、DLLアタッチ時に、リソースのロードをしているので、そこでDLLのパス保存もしておきましょう。~

自分自身のパスを得るには、DllMainの第1引数を指定して、GetModuleFileNameをコールします。~
通常、実行ファイルが自分自身のパスを得るには、第1引数にNULLを指定してGetModuleFileNameをコールしますが、DLLでそれをやると、DLLを呼び出した実行ファイルの名前が取れてきてしまいます。~
(Todayプラグインだと、「\Windows\shell32.exe」)~
今回は、DLL自身のパスを知りたいので、ちゃんとDLLのインスタンスハンドルを渡しましょう。~

実際に画像ファイルのパスを作りたい時には、このDLLのパスをベースに、ファイル名を入れ替えればいい事になります。~
本当は、makepathやsplitpathといった関数が使用できれば楽でよかったのですが、見あたらなかったので、自力で入れ替えています。~
処理としては、後ろから文字を調べて、最初に「\」が出てきたところより後ろを、読みたいファイル名に置き換えています。~

 // パス作成
 bool CApp::makePath( LPTSTR path, LPCTSTR fname )
 {
 	int	len	= 0;
 	int	len2	= 0;
 	int	i	= 0;
 
 	if( NULL == path || NULL == fname ){
 		return false;
 	}
 
 	if( NULL == ::lstrcpy( path, m_dllPath )){
 		return false;
 	}
 
 	len = ::lstrlen( path );
 	len2 = ::lstrlen( fname );
 	if( 0 >= len || 0 >= fname ){
 		return false;
 	}
 	if( MAX_PATH < len + len2 + 1 ){
 		return false;
 	}
 
 	for( i = 0; i < len; i++ ){
 		if( TEXT( '\\' ) == path[len-i-1] ){
 			break;
 		}
 	}
 	if( i == len ){
 		return false;
 	}
 
 	if( NULL == ::lstrcpy( path + len - i, fname )){
 		return false;
 	}
 
 	return true;
 }

** DIBSection [#e399e3eb]

PNGファイルをロードする前に、ロードした画像を保存しておく領域が必要です。~
しかも、ただ保存しておくだけではなく、そこから裏画面に転送(描画)が可能な必要があります。~
さらに、今回はそこまではやりませんが、ピクセルデータをメモリ上で直接いじれると、色々と便利です。~
というわけで、これらが可能なDIBSectionを作成することにします。~

今回は、ロード用のDIBSectionだけではなく、今まで表画面との互換性を持った状態だった裏画面もDIBSectionにします。~
将来的にやりたい(やるかもしれない)事として、ロードした画像と裏画面のピクセルを、直接メモリ上の値をいじってBlendしたいというものがあります。~
この時、裏画面の形式が表画面によって変わってしまうと、Blendする際に、色々な形式の組み合わせの計算式を用意する必要が出てきてしまいます。~

裏画面はそのまま表画面へと転送するので、透明度を持つ必要はなく、24bitのRGB。~
画像をロードする領域は、透明度を扱える必要があるので、32bitのARGBで作成します。~
もちろん、ロードする画像に透明度がないようであれば、こちらも24bitで構いません。~
このように固定しておけば、RGBのそれぞれが1バイトずつのBlendが出来、画像側で1バイトのαを考慮するかどうかだけの切り分けですむため、作る処理は少なくて済みます。~

ただし、今まで裏画面は表画面との互換性があるものを作成していましたが、固定で24bitのものとすることにより、裏画面と表画面の形式が異なってしまいます。~
異なるとどうなるかというと、裏画面を表画面に転送(BitBlt)する際のパフォーマンスが落ちるので、他のアイテムに比べて、一瞬遅れて表示されるように見えるかもしれません。~

まぁ、ゲーム等の頻繁に画面の書き換えが発生するような用途の場合には、このあたりを考慮してやり方を工夫する必要があると思いますが、必要なときに必要なところだけ書き換えればいいようなTodayアイテムであれば、あまり気にしなくてもいいでしょう。~

DIBSection作成処理は、以下のような感じです。~

 // DIBセクション作成
 bool CDIBSection::create( HWND hwnd, int width, int height, int cbit )
 {
 	HDC	hdc	= NULL;		// Windowデバイスコンテキストハンドル
 
 	destroy();
 
 	// デバイスコンテキストの作成
 	hdc = ::GetDC( hwnd );
 	if( NULL == hdc ){
 		goto Error;
 	}
 	m_hdc = CreateCompatibleDC( hdc );
 	if( NULL == m_hdc ){
 		goto Error;
 	}
 
 	// BITMAPINFOHEADER構造体の初期化
 	ZeroMemory( &m_bi, sizeof( m_bi ));
 	m_bi.biSize		= sizeof( m_bi );
 	m_bi.biWidth		= width;
 	m_bi.biHeight		= height;
 	m_bi.biPlanes		= 1;
 	m_bi.biBitCount		= cbit;
 	m_bi.biCompression	= BI_RGB;
 	m_bi.biSizeImage	= 0;
 	m_bi.biXPelsPerMeter	= 0;
 	m_bi.biYPelsPerMeter	= 0;
 	m_bi.biClrUsed		= 0;
 	m_bi.biClrImportant	= 0;
 
 	// DIBSectionの作成
 	m_hBitmap = CreateDIBSection( m_hdc, ( LPBITMAPINFO )&m_bi, DIB_RGB_COLORS, ( LPVOID* )&m_dibmem, NULL, 0 );
 	if( NULL == m_hBitmap ){
 		goto Error;
 	}
 
 	// ビットマップの関連付け
 	m_hOldBitmap = SelectBitmap( m_hdc, m_hBitmap );
 	if( NULL == m_hOldBitmap ){
 		goto Error;
 	}
 
 	if( hdc ){
 		::ReleaseDC( hwnd, hdc );
 		hdc = NULL;
 	}
 	return true;
 
 Error:
 	destroy();
 	if( hdc ){
 		::ReleaseDC( hwnd, hdc );
 		hdc = NULL;
 	}
 	return false;
 }

** IImagingFactory [#o39ac0df]

透明度付きの画像をロードするには、SHLoadImageFileではなく、COMのIImagingFactoryを使用しなければならないようです。~
このため、まずはCOMを初期化する必要があり、CoInitializeExをコールしなければなりません。~

CoInitializeExとCoUninitializeはペアで呼び出す必要があり、スレッドに対して呼び出す必要があります。~
しかし、DllMainのプロセスアタッチ/デタッチはスレッド毎ではなく、スレッドのアタッチ/デタッチは必ず全てのスレッドに対してコールされるわけではないので、安全ではありません。~

このため、COMの初期化/解放は、Windowに同期して行うようにします。~
InitializeCustomItemがコールされて、Windowを作成しようとする時に、CoInitializeExで初期化を行い、WM_DESTROYでWindowが破棄されるタイミングで、CoUninitializeを呼び出します。~
WindowプロシージャのWM_CREATEとWM_DESTROYは、毎回同じスレッドから呼び出されるようなので、このタイミングで問題ないと思います。~
InitializeCustomItemがコールされて、Windowを作成しようとする時に、CreateWindow内でコールバックされるWM_CREATEのタイミングでCoInitializeExをコールして初期化を行い、WM_DESTROYでWindowが破棄されるタイミングで、CoUninitializeを呼び出します。~

画像データは、本来であればリソースとして、Dllのプロセスアタッチ時にロードして、何回Windowが作り直されたとしても、使い回しが出きるべきですが、今回はCOMをWindowに同期させて初期化するため、アタッチ時にはCOMがなく、ロードが出来ません。~
また、ロードを行うWindowと互換性のあるデバイスコンテキストに関連付けたDIBSection上にロードを行うため、画像データもWindowに同期させて持つようにします。~
TodayアイテムのWindowは、何個も生成されるようなものではないため、Windowに同期させて持っても、容量的な問題はないはずです。~

COMからPNGファイルをロードして保持する手順としては、CoCreateInstanceでCLSID_ImagingFactoryを指定してIImagingFactoryを取得し、画像ファイルのパスを指定してIImageを取得します。~
IImageからImageInfoの画像情報を取得し、画像情報からサイズや色深度を取得して、対応するDIBSectionを作ります。~
DIBSectionができたら、IImageからDIBSectionのデバイスコンテキストに描画を行ないます。~

この辺りのソース的な流れは以下のような感じです。~

 // イメージロード
 bool CTodayWnd::loadImage( HWND hwnd, LPCDIBSection lpCDib, LPCTSTR path )
 {
 	IImagingFactory	*lpImageFactory	= NULL;		// ImagingFactoryインターフェイスへのポインタ
 	IImage		*lpImage	= NULL;		// Imageインターフェイスへのポインタ
 	ImageInfo	imageInfo;			// 画像情報
 	RECT		rect;				// 描画矩形
 	bool		ret		= false;	// 戻り値
 
 	// ImagingFactoryインスタンス生成
 	if( S_OK != ::CoCreateInstance( CLSID_ImagingFactory, NULL, CLSCTX_INPROC_SERVER, IID_IImagingFactory, ( LPVOID* )&lpImageFactory )){
 		goto Error;
 	}
 	if( NULL == lpImageFactory ){
 		goto Error;
 	}
 
 	// 画像ファイル読み込み
 	if( S_OK != lpImageFactory->CreateImageFromFile( path, &lpImage )){
 		goto Error;
 	}
 	if( NULL == lpImage ){
 		goto Error;
 	}
 
 	// 画像情報取得
 	ZeroMemory( &imageInfo, sizeof( imageInfo ));
 	if( S_OK != lpImage->GetImageInfo( &imageInfo )){
 		goto Error;
 	}
 
 	// DIBセクション作成
 	if( !lpCDib->create( hwnd, &imageInfo )){
 		goto Error;
 	}
 
 	// DIBセクションに描画
 	ZeroMemory( &rect, sizeof( rect ));
 	rect.right	= imageInfo.Width;
 	rect.bottom	= imageInfo.Height;
 	if( S_OK != lpImage->Draw( m_digital.getDC(), &rect, NULL )){
 		goto Error;
 	}
 
 	ret = true;
 
 Error:
 	if( NULL != lpImage ){
 		lpImage->Release();
 		lpImage = NULL;
 	}
 	if( NULL != lpImageFactory ){
 		lpImageFactory->Release();
 		lpImageFactory = NULL;
 	}
 	return ret;
 }

色深度は、ImageInfoのPixelFormatの、PixelFormatAlphaが立っている場合は透過画像なので32bitのDIBSectionを、立っていない場合は非透過画像なので、24bitのDIBSectionを作るようにします。~

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 単語検索 最終更新   ヘルプ   最終更新のRSS