FAQ2-2

Q. 256色パレットを使用して描画するには

 256色ビットマップを表示する方法がわからなくて困っています.ビットマップを表示する関数(BitBltやStretchBlt)を使用しても16色でしか表示されません.
 .BMPファイルを読み込んで,デバイス非依存ビットマップ(DIB)形式のまま,StretchDIBitsを使用してみたのですが,やはり16色でしか表示されませんでした.おそらくパレットを操作する必要があると思うのですが,方法がわかりません.


A. CreatePalette()でパレットを作成し,RealizePalette()を呼ぶ

 256色ビットマップを表示するには,パレットを操作する必要があります.まず,ビットマップの描画の前に,256色パレットを扱う方法を説明しましょう.

256色パレットを扱う方法

 256色と簡単に言いますが,Windowsアプリケーションは,16色,256色,フルカラーなど,ありとあらゆる発色モードで動作する必要があります.
 256色モードを除く多くの発色モードは,固定パレットと呼ばれ(注1),複数のアプリケーションが同時にいろいろな色を使おうとしても,そのまま最も近い色に変換して表示してくれます.
 可変パレットである256色モードでは,256色のそれぞれ1色1色,1677万色のうちから好きな色に変えることができます.例えば,256色のうち200色を200段階の赤のグラデーションにすることも可能です.
 そこで,複数のアプリケーションが同時にいろいろな色を使おうとするとパレットの競合が起こります.とは言っても,パレット操作をしない限りシステムカラー(20色)を固定パレットとして,20色に最も近い色に変換してくれます(図2-2).

[図2-2] :

 つまり,256色の可変パレットであっても,システムカラーは予約されており,確保できるパレットは236色しか残されていません(本当はシステムカラーに予約されている色の数は,GetDeviceCaps(hdc,NUMRESERVED) で得られる数ですが,すべて20色と考えてよい).
 アプリケーションは,自分がアクティブなとき(正確には入力フォーカスがあるとき),この残された236色を100%使用することができます.この状態を「前景パレットとして実体化している」と言います.
 逆に非アクティブなときは,いま利用できる色の中から最も近い色を使用して描画します.この状態を「背景パレットとして実体化している」と言います.
と,そういった考えをふまえた上で,まずはパレットの作成をしてみます.

パレットの作成

 リスト2-2はパレットを作成するサンプルの関数です.パレットの作成にはCreatePalette関数を使用します.
 ここでは,黒〜白32色,赤32色,緑32色,青32色の計128色のパレットを作成します.
 こうしてできたパレットは「論理パレット」と呼ばれます.通常,peFlagsメンバにはNULLを入れます.ここにPC_RESERVEDを入れると,背景パレットで実体化しようとしている他のアプリケーションはその色を利用できなくなります.
 PC_RESERVEDにしたからといって,他のアプリケーションが236色フルに使用できなくなるわけではありません.
 こうしてできた論理パレットは,RealizePalette関数を呼ぶまで,システムパレットに影響を与えません.システムパレットとは,実際に画面に割り当てられているパレットのことです.
 RealizePaletteを呼ぶタイミングは,まずはWM_PAINTメッセージを処理するハンドラで行いましょう.

[リスト2-2] :

HPALETTE PASCAL sample_CreatePal(void)
{
	BYTE		xlogpal[sizeof(LOGPALETTE)+sizeof(PALETTEENTRY)*256 ];
	LOGPALETTE FAR *	logpal;
	HPALETTE	hPal;
	int		count, index;

	logpal = (LOGPALETTE FAR *)xlogpal;
	logpal->palVersion    = 0x0300;
	index = 0;
	for(count=0; count<32; count++){
		logpal->palPalEntry[index].peRed   = count*255/31;
		logpal->palPalEntry[index].peGreen = count*255/31;
		logpal->palPalEntry[index].peBlue  = count*255/31;
		logpal->palPalEntry[index].peFlags = NULL;
		index++;
	}
	for(count=0; count<32; count++){
		logpal->palPalEntry[index].peRed   = count*255/31;
		logpal->palPalEntry[index].peGreen = 0;
		logpal->palPalEntry[index].peBlue  = 0;
		logpal->palPalEntry[index].peFlags = NULL;
		index++;
	}
	for(count=0; count<32; count++){
		logpal->palPalEntry[index].peRed   = 0;
		logpal->palPalEntry[index].peGreen = count*255/31;
		logpal->palPalEntry[index].peBlue  = 0;
		logpal->palPalEntry[index].peFlags = NULL;
		index++;
	}
	for(count=0; count<32; count++){
		logpal->palPalEntry[index].peRed   = 0;
		logpal->palPalEntry[index].peGreen = 0;
		logpal->palPalEntry[index].peBlue  = count*255/31;
		logpal->palPalEntry[index].peFlags = NULL;
		index++;
	}
	logpal->palNumEntries = index;
	hPal = CreatePalette((LPLOGPALETTE)logpal);
	
	return(hPal);
}

WM_PAINTメッセージ処理

 リスト2-3は,WM_PAINTメッセージ処理の例です.RealizePaletteを呼ぶ前に,SelectPaletteを呼んで,デバイスコンテキストにパレットを選択します.SelectPaletteの最後のパラメータ(FALSE)は,パレットを背景パレットとして選択するかどうかのフラグです.
 通常,FALSE(=前景パレット)として選択しておけば,アクティブなとき前景パレットになり,非アクティブなとき背景パレットになるので問題ありません.SelectPaletteは,Windowsプログラミングのマナーとして,対になって呼びましょう.
 これで,パレットを使用した描画が可能となります.注意すべきなのは,GDI関数でパラメータにCOLORREF型のあるものは,RGB値を指定するだけではシステムカラーを使用してしまうという点です.
 パレットに最も近い色を指定したい場合は,PALETTEINDEXマクロと,GetNearestPaletteIndex関数を使用しなければなりません.
 しかし,WM_PAINTを処理するだけではパレット操作は終わりではありません.WM_PALETTECHANGEDとWM_QUERYNEWPALETTEというメッセージも処理する必要があります.

[リスト2-3] :

	case WM_PAINT:{
		PAINTSTRUCT	ps;
		HDC		hdc;
		HPALETTE	hOldPal;
		RECT		rc;
		COLORREF	dwColor;
		BYTE		nLevel;
		HPEN		hPen;
		HANDLE	hOld;
		int		i;
		
		hdc = BeginPaint(hwnd, &ps);
		hOldPal = SelectPalette(hdc, hPal, FALSE);
		RealizePalette(hdc);
		
		GetClientRect(hwnd, &rc);
		for( i=0; i < rc.bottom; i++ ){
			nLevel = (BYTE)((long)i*255/rc.bottom);
			dwColor = RGB(nLevel,nLevel,nLevel);
			
			hPen = CreatePen(PS_SOLID,1, PALETTEINDEX(GetNearestPaletteIndex(hPal,dwColor)));
			hOld = SelectObject(hdc,hPen);
			MoveTo(hdc,0,i);
			LineTo(hdc,rc.right,i);
			SelectObject(hdc,hOld);
			DeleteObject(hPen);
		}
		
		SelectPalette(hdc, hOldPal, FALSE);
		EndPaint(hwnd, &ps);
		
		return(0);
		}

WM_QUERYNEWPALETTEメッセージ処理

 リスト2-4にWM_QUERYNEWPALETTEメッセージ処理の例を示します.WM_PALETTECHANGEDメッセージは,RealizePaletteでパレットが実体化したとき,すべウィンドウに送られます.
 これは,ほかのアプリケーションがRealizePaletteしたときに,自分を背景パレットで再描画するために処理します.
 WM_QUERYNEWPALETTEメッセージは,ウィンドウが入力フォーカスを受け取って,前景パレットとして再描画する必要があるときに送られます.ここでは,RealizePaletteしてみて,システムパレットが変更された場合,ウィンドウを再描画するようにしています.これはパレットを使用しているアプリが自分一つだけのときや,ほかのアプリがまったく同じパレットを使用している場合,システムパレットは変わらないので,再描画する必要がないためです.
と,こういったことをふまえた上で,いよいよビットマップです.長い(^^;

[リスト2-4] :

		case WM_PALETTECHANGED:
			if (wParam == hwnd) return 0L;
			InvalidateRect(hwnd, NULL, TRUE);
			return(0);
			
		case WM_QUERYNEWPALETTE:{
			HDC			hdc;
			HPALETTE	hOldPal;
			UINT		nRealized;
			
			hdc = GetDC(hwnd);
			hOldPal = SelectPalette(hdc, angl_hPal, FALSE);
			nRealized = RealizePalette(hdc);
			SelectPalette(hdc, hOldPal, FALSE);
			ReleaseDC(hwnd,hdc);
			
			if(nRealized>0){
				InvalidateRect(hwnd, NULL, TRUE);
			}
			
			return(1);
			}

ビットマップの描画

 ここまでの考えがわかれば,ビットマップはお手のものです.
 通常,ビットマップファイル(.BMP)は,BITMAPFILEHEADER構造体とBITMAPINFO構造体から構成されています.BITMAPINFO構造体は,DIB形式そのものです.詳細はSDKヘルプなどを見てください.
 作成する論理パレットのデータはDIBの中に含まれています.注意すべきなのは,パレットを使用しているのは256色のビットマップだけではないということです.
 また,BITMAPINFO構造体のbmiHeader.biSizeの値によって参照する構造体も変化したり,圧縮形式なんてものもあったりで,注意することが山ほどあります.このあたりのことは,SDKヘルプを徹夜して見続ければなんとかなるでしょう(笑).
 また,RealizePaletteするのは描画のときだけではなく,CreateBitmapを呼んでビットマップを作成するときにもやっておかなくてはいけません.これは意外な落とし穴なので注意しましょう.

Back to FAQ main page