パレット


256色ディスプレイ

現在のディスプレイのほとんどが、24 ビットカラー (true color)
または、16 ビットカラー (high color) モードをサポートしています

しかし、古いコンピュータなどでは 8 ビットカラー しか提供していないことがあります
8 ビットカラーとは、同時発色数 256 色で、1 ピクセルに 8 ビットの情報量が必要なモードです
Windows は、256 色でも動作するように設計されています

ソフトウェア開発者は、256 色モードで実行されることも想定しなければなりません
実際、Windows 初期の時代は 256 色が標準だったし
古いゲームソフトなどでは、256 色で最適化されているものが多く存在します

256 色とは、とても微妙で繊細な数です
4 ビットカラーの 16 色モードでは、あまりにも色が少なすぎますし
16 ビット以上のフルカラーモードでは、実世界の色を十分に表現できるでしょう

256 色は、実世界の色を表現するにはある程度かなった色数です
しかし、256 色全ての色を固定してしまえば、色は足りなくなるでしょう
誰かが決めた 256 色の中に、あなたが使いたい色は無いかもしれないのです

24 ビットで表現される数百万色の色の中から、あなたが 256 色をチョイスできれば
最低限、ソフトウェアや画像の表示に必要な色は確保できるでしょう
そう、これを実現するための機能こそがパレットなのです
フルカラーが当たり前の新規ユーザーにとって、むしろこれは難しく感じるかもしれません
MS と IBM の歴史を見てきた古株のユーザーであれば、言われるまでもない常識でしょう

8 ビットカラーの場合、1ピクセルの情報が表すのは色ではない
8 ビットの各ピクセルの情報は、パレットの参照番号を表しています
ハードウェア、またはシステムにはパレット参照テーブルというものがあります
この参照テーブルには 0 〜 255 番までのリストがあり、それぞれが特定の色を参照します

あらかじめ、0 番は赤、1 番は青と決めておけば
ピクセルビットが 0 のとき、ディスプレイは参照テーブルを使って赤と判断します
ソフトウェアでエミュレートするなら、パレットは COLORREF の配列で表現されていて
ピクセルビットはその配列の添え字をあらわしていると考えることができます

ピクセルビット(8bit)
パレット参照テーブル
アナログ変換

パレット参照テーブルが参照する RGB 値は、18 ビットカラー、24 ビットカラーなどがあり
ここから先は、ハードウェアの世界なので開発者の領域ではないでしょう

Windows アプリケーションの場合、この参照パレットに直接アクセスするわけにはいきません
ハードウェアにアクセスできるのは、システムレベルのプログラムです
さらに、Windows の場合は別の問題も現れます

複数のアプリケーションが同時に一つのディスプレイ上に表示される Windows では
一つのアプリケーションのためにパレットの 256 色全てを使ってしまっては
他のアプリケーションや、システムが正常な色で実行されなくなります

そこで、Windows はシステムパレットとして 20 色を予約する方法を用いました
これらの色は、基本的にアプリケーションが操作することはできず(可能ではある)
アプリケーションは、残り 236 色を自由に扱うことが許されている
以下の表は、Windows が予約しているパレットの色とピクセルビットです

ピクセルビットRGB
000000000x000000ブラック
000000010x800000ダークレッド
000000100x008000ダークグリーン
000000110x808000ダークイエロー
000001000x000080ダークブルー
000001010x800080ダークマゼンタ
000001100x008080ダークシアン
000001110xC0C0C0ライトグレー
000010000xC0DCC0マネーグリーン
000010010xA6CAF0スカイブルー
111111110xFFFFFFホワイト
111111100x00FFFFシアン
111111010xFF00FFマゼンタ
111111000x0000FFブルー
111110110xFFFF00イエロー
111110100x00FF00グリーン
111110010xFF0000レッド
111110000x808080ダークグレー
111101110xA0A0A4ミディアムグレー
111101100xFFFBF0クリーム

さらに、異なるアプリケーションが複数表示されている場合は
アクティブなウィンドウを最優先に、パレットを割り当てる権利が与えられます

Windows は、パレットをサポートしていないアプリケーションが GDI の描画を行うと
指定された RGB からシステムパレットに最も近い色を割り出して、そのパレットを用います
256 色モードでビットマップを表示すると、よくわかるでしょう

試しに、ビデオモードを 256 色に変更して、次のプログラムを作成し、実行してください
「画面のプロパティ」の「設定」タブで変更することができます
#include <windows.h>

LRESULT CALLBACK WndProc(HWND hWnd , UINT msg , WPARAM wp , LPARAM lp) {
	HDC hdc;
	PAINTSTRUCT ps;
	HBRUSH hBrush;
	int count , x = 20 , y = 10;
	static COLORREF color[65];

	switch (msg) {
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	case WM_CREATE:
		for (count = 0 ; count < 64 ; count++)
			color[count] = RGB(4 * count , 4 * count , 4 * count);
		color[64] = RGB(255 , 255 , 255);
		return 0;
	case WM_PAINT:
		hdc = BeginPaint(hWnd , &ps);
		for (count = 0 ; count < 65 ; count++ , x += 20) {
			hBrush = CreateSolidBrush(color[count]);
			SelectObject(hdc , hBrush);

			Rectangle(hdc , x , y , x + 10 , y + 40);
			if (x > 400) { y += 60; x = 0; }
			SelectObject(hdc , GetStockObject(WHITE_BRUSH));
			DeleteObject(hBrush);
		}
		EndPaint(hWnd ,&ps);
		return 0;
	}
	return DefWindowProc(hWnd , msg , wp , lp);
}

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
			PSTR lpCmdLine , int nCmdShow) {
	HWND hWnd;
	MSG msg;
	WNDCLASS winc;

	winc.style 		= CS_HREDRAW | CS_VREDRAW;
	winc.lpfnWndProc	= WndProc;
	winc.cbClsExtra	= winc.cbWndExtra	= 0;
	winc.hInstance		= hInstance;
	winc.hIcon		= LoadIcon(NULL , IDI_APPLICATION);
	winc.hCursor		= LoadCursor(NULL , IDC_ARROW);
	winc.hbrBackground	= (HBRUSH)GetStockObject(WHITE_BRUSH);
	winc.lpszMenuName	= NULL;
	winc.lpszClassName	= TEXT("KITTY");

	if (!RegisterClass(&winc)) return 1;

	hWnd = CreateWindow(
			TEXT("KITTY") , TEXT("Kitty on your lap") ,
			WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			NULL , NULL , hInstance , NULL
	);

	if (hWnd == NULL) return 1;

	while (GetMessage(&msg , NULL , 0 , 0 )) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return msg.wParam;
}


このプログラムは、ループを用いて黒から白に向かって RGB 値を上げて
グレーシェードをクライアントウィンドウに表示するというプログラムです

フルカラーモードでは、黒から白へブラシの色が綺麗なグラデーションとなりますが
256 色で実行すると、上の図のように表示されます
色を見ると、色の濃さを異なる色を混ぜて表現していることがわかります

これは、グラフィック技術におけるディザ法という方式です
ディザとは、ある色を表現するのに複数の異なる色を交互に表示して表現する方法で
例えば、赤と青を交互に表示して紫を表現するのもディザ法といえます

このように、Windows はブラシに対して 256 色の場合はディザ法を用います
また、線や文字の場合はブラック、ダークグレー、ライトグレー、白の4色のいずれかを用います
ビットマップの場合は、予約されているパレットの色を駆使して表示されることとなります


パレットマネージャー

Windows が、パレットをどのように使っているかはわかってもらえたでしょうか
では、さらにアプリケーションがパレットを使う方法を説明します

私たちが作る一般アプリケーションは、システムに介入することはできません
ディスプレイの参照テーブルを操作できるのは Windows です
私たちは、Windows が提供するファンクションを用いて、使いたい色を要求します
そうすれば、Windows パレットマネージャーという機能が指定した色を設定する仕掛です

まず、デバイスコンテキストにパレットを設定する必要があります
そのためには、論理パレットを作成してそのハンドルを取得しなければなりません
論理パレットとは、ディスプレイには反映されないメモリ上のパレットです

論理パレット->システムパレット
(Windows が管理するパレット)
->デバイスパレット
(物理デバイスのパレット)

実際にパレットを扱うと、このような流れになります
論理パレットを作成するには CreatePalette() を使います

HPALETTE CreatePalette(CONST LOGPALETTE *lplgpl);

lplgpl には論理パレットの情報が格納されている
LOGPALETTE 構造体へのポインタを指定します
関数が成功すると論理パレットのハンドル、失敗すると NULL が返ります

LOGPALETTE 構造体は、次のように定義されています
typedef struct tagLOGPALETTE { // lgpl 
    WORD         palVersion; 
    WORD         palNumEntries; 
    PALETTEENTRY palPalEntry[1]; 
} LOGPALETTE; 
palVersion には、構造体の Windows バージョン番号を指定します
現在では、必ず 0x0300 (Windows 3.0 を示す) を指定します
palNumEntries は、パレットテーブルのエントリー数(色数)を指定します
例えば、236 色のうち 40 色用いるのであれば、40 と指定します

最後の palPalEntry は、パレットのエントリーごとの色を表す情報です
PALETTEENTRY 構造体は次のように定義されています
typedef struct tagPALETTEENTRY { // pe 
    BYTE peRed; 
    BYTE peGreen; 
    BYTE peBlue; 
    BYTE peFlags; 
} PALETTEENTRY;
peRed は赤、peGreen は緑、peBlue は青の色素を表します
それぞれ、0 〜 0xFF までの値で、色の強さを指定します

peFlags はパレットエントリの使用方法を表す定数を指定します
以下の定数、もしくは指定しない場合は NULL を指定できます

定数解説
PC_EXPLICIT論理パレットの下位ワードは
ハードウェアパレットインデックスを表す
PC_NOCOLLAPSEカラーをシステムパレットの既存カラーにマッチさせるのではなく
システムパレットの未使用エントリに配置する
未使用エントリがなければ、カラーを通常のようにマッチさせる
PC_RESERVED論理パレットエントリを、パレットアニメーションのために使用する

パレットアニメーションについては、後ほど詳しく説明します

LOGPALETTE 構造体の palPalEntry メンバは1つの要素を持つ配列として宣言されています
これは、BITMAPINFO 構造体同様に、動的に配列を扱う手段として宣言されているのです
このため、一般的に LOGPALETTE 構造体はポインタ型として扱います

つまり、LOGPALETTE 構造体のサイズに
PALETTEENTRY 構造体のサイズ × エントリ数を加算した分のメモリ領域を
malloc() 等でヒープに確保し、LOGPALETTE 構造体型のポインタ変数にアドレスを代入します
これで、palPalEntry メンバをエントリ数分の配列としてアクセスすることができます

論理パレットのハンドルを CreatePalette() で作成したならば
次は、描画する時にデバイスコンテキストに論理パレットを設定します
論理パレットを選択するには SelectPalette() を使います
HPALETTE SelectPalette(
	HDC hdc,
	HPALETTE hpal,
	BOOL bForceBackground
);
hdc にはデバイスコンテキストのハンドル
hpal には論理パレットのハンドルをそれぞれ指定します

bForceBackground は、ブール値で論理パレットをシステムカラーにマップするかを指定します
TRUE を指定した場合、論理パレットは常に物理パレットに最前の形でマップされます
FALSE を指定すれば、アプリケーションがフォアグラウンドにある時
論理パレットはデバイスパレットにコピーされるようになります
関数が成功すれば、以前の論理パレットのハンドルが、失敗すれば NULL が返ります

これで、デバイスコンテキストに論理パレットを設定することはできました
しかし、まだ物理パレットへの転送は行われていないのでパレットは反映されていません
デバイスコンテキストの論理パレットをシステムパレットにセットするには
RealizePalette() 関数を用いる必要があります

UINT RealizePalette(HDC hdc);

hdc は選択された論理パレットに対応するデバイスコンテキストのハンドルを指定します
戻り値は、システムパレットにマップされた論理パレットのエントリ数になります
関数が失敗した場合 GDI_ERRO が返ります
この関数を用いて、論理パレットをシステムパレットに配置することを"リアライズ"と言います

ここで選択したデバイスコンテキストがディスプレイならば
システムパレットと同時にディスプレイアダプタの物理パレットにも反映されます
メモリデバイスコンテキストならば、選択されているビットマップのカラーテーブルが変更されます

Windows は、限られた 256 色を複数のウィンドウが効率よく利用できるように
論理パレットのエントリにシステムパレットと同じ色が存在する場合
その色を既存のシステムパレットにマップするなどの最適化が図られます

さて、論理パレットを作成しこれをシステムパレットにリアライズできれば
GDI ファンクションによって、パレットの色を用いて描画することができるようになる
ただし、色を指定するのは従来と同じく COLORREF 型の値ですが
RGB マクロが返す値では、パレットにアクセスできない

パレットにアクセスするには PALETTERGB マクロを使います
このマクロは次のように定義されています

#define PALETTERGB(r, g, b) (0x02000000 | RGB(r, g, b))

最上位バイトが 2 になっていることに注意してもらいたい
この場合、システムはこの色に最も近いパレットインデックスを参照します
もしパレットモードでなかった場合は、通常のフルカラーモードとして RGB 値を使います
RGB モードとパレットモードの両方で動作するアプリケーションを作る時に便利でしょう
構文は、次のように定義されています

COLORREF PALETTERGB(BYTE bRed , BYTE bGreen , BYTE bBlue);

bRed は赤、bGreen は緑、bBlue は青の色素の強さを指定します
このマクロの戻り値を使えば、システムはデバイスコンテキストの論理パレットから
最もこの色に近い色を参照するようになります
#include <windows.h>

LRESULT CALLBACK WndProc(HWND hWnd , UINT msg , WPARAM wp , LPARAM lp) {
	HDC hdc;
	PAINTSTRUCT ps;
	HBRUSH hBrush;
	LOGPALETTE *lpPalette;
	int count , x = 20 , y = 10;
	static HPALETTE hPalette;
	static COLORREF color[65];
	
	switch (msg) {
	case WM_DESTROY:
		DeleteObject(hPalette);
		PostQuitMessage(0);
		return 0;
	case WM_CREATE:
		lpPalette = malloc(sizeof (LOGPALETTE) + 64 * sizeof (PALETTEENTRY));
		lpPalette->palVersion = 0x0300;
		lpPalette->palNumEntries = 65;

		for (count = 0 ; count < 65 ; count++) {
			color[count] = PALETTERGB(4 * count , 4 * count , 4 * count);
			lpPalette->palPalEntry[count].peRed =
			lpPalette->palPalEntry[count].peGreen =
			lpPalette->palPalEntry[count].peBlue = (BYTE) 4 * count;
			lpPalette->palPalEntry[count].peFlags = NULL;
		}
		color[64] = PALETTERGB(255 , 255 , 255);
		lpPalette->palPalEntry[64].peRed =
		lpPalette->palPalEntry[64].peGreen =
		lpPalette->palPalEntry[64].peBlue = 255;

		hPalette = CreatePalette(lpPalette);
		free(lpPalette);
		return 0;
	case WM_PAINT:
		hdc = BeginPaint(hWnd , &ps);
		SelectPalette(hdc , hPalette , FALSE);
		RealizePalette(hdc);
		for (count = 0 ; count < 65 ; count++ , x += 20) {
			hBrush = CreateSolidBrush(color[count]);
			SelectObject(hdc , hBrush);

			Rectangle(hdc , x , y , x + 10 , y + 40);
			if (x > 400) { y += 60; x = 0; }
			SelectObject(hdc , GetStockObject(WHITE_BRUSH));
			DeleteObject(hBrush);
		}
		EndPaint(hWnd ,&ps);
		return 0;
	
	}
	return DefWindowProc(hWnd , msg , wp , lp);
}

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
			PSTR lpCmdLine , int nCmdShow) {
	HWND hWnd;
	MSG msg;
	WNDCLASS winc;

	winc.style 		= CS_HREDRAW | CS_VREDRAW;
	winc.lpfnWndProc	= WndProc;
	winc.cbClsExtra	= winc.cbWndExtra	= 0;
	winc.hInstance		= hInstance;
	winc.hIcon		= LoadIcon(NULL , IDI_APPLICATION);
	winc.hCursor		= LoadCursor(NULL , IDC_ARROW);
	winc.hbrBackground	= (HBRUSH)GetStockObject(WHITE_BRUSH);
	winc.lpszMenuName	= NULL;
	winc.lpszClassName	= TEXT("KITTY");

	if (!RegisterClass(&winc)) return 1;

	hWnd = CreateWindow(
			TEXT("KITTY") , TEXT("Kitty on your lap") ,
			WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			NULL , NULL , hInstance , NULL
	);

	if (hWnd == NULL) return 1;

	while (GetMessage(&msg , NULL , 0 , 0 )) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return msg.wParam;
}


これは、先ほどのプログラムをパレットに対応させたプログラムです
今度は、65 色のグレーシェードをパレットに登録しているので
ディザではなく、綺麗なグラデーションが確認できるでしょう

このプログラムでは、簡単を実現するために細かなビデオモードのチェックを省略しているが
GetDeviceCaps() 関数で、パレットがサポートされているかどうかなどを調べることができます
パレットに依存するプログラムを書く場合は、必ずチェックするべきです

論理パレットにどのような色が、どのインデックスに格納されているかわからない場合や
フルカラーとの併用を考えない、パレットに依存したプログラムを書く場合
上のような、近似色を検索するやり方はむしろ冗長に感じるでしょう

そこで、Windows は論理パレットのインデックスを直接指定する方法もサポートしています
COLORREF の上位バイトが 1 のとき、下位ワードは論理パレットのインデックスと判断されます
これには PALETTEINDEX() マクロを使用します

COLORREF PALETTEINDEX(WORD wPaletteIndex);

wPaletteIndex には、論理パレットのインデックスを指定します
このインデックスは、LOGPALETTE 構造体の palPalEntry メンバの配列要素を表すもので
ディスプレイやシステムのパレットを表すインデックスではありません
このマクロは、次のように定義されています

#define PALETTEINDEX(i) / ((COLORREF) (0x01000000 | (DWORD) (WORD) (i)))

最上位バイトが 1 、下位ワードがパレットインデックスを表しています
#include <windows.h>

LRESULT CALLBACK WndProc(HWND hWnd , UINT msg , WPARAM wp , LPARAM lp) {
	HDC hdc;
	PAINTSTRUCT ps;
	HBRUSH hBrush;
	LOGPALETTE *lpPalette;
	int count , x = 20 , y = 10;
	static HPALETTE hPalette;
	
	switch (msg) {
	case WM_DESTROY:
		DeleteObject(hPalette);
		PostQuitMessage(0);
		return 0;
	case WM_CREATE:
		lpPalette = malloc(sizeof (LOGPALETTE) + 64 * sizeof (PALETTEENTRY));
		lpPalette->palVersion = 0x0300;
		lpPalette->palNumEntries = 65;

		for (count = 0 ; count < 65 ; count++) {
			lpPalette->palPalEntry[count].peRed =
			lpPalette->palPalEntry[count].peGreen =
			lpPalette->palPalEntry[count].peBlue = (BYTE) 4 * count;
			lpPalette->palPalEntry[count].peFlags = NULL;
		}
		lpPalette->palPalEntry[64].peRed =
		lpPalette->palPalEntry[64].peGreen =
		lpPalette->palPalEntry[64].peBlue = 255;

		hPalette = CreatePalette(lpPalette);
		free(lpPalette);
		return 0;
	case WM_PAINT:
		hdc = BeginPaint(hWnd , &ps);
		SelectPalette(hdc , hPalette , FALSE);
		RealizePalette(hdc);
		for (count = 0 ; count < 65 ; count++ , x += 20) {
			hBrush = CreateSolidBrush(PALETTEINDEX(count));
			SelectObject(hdc , hBrush);

			Rectangle(hdc , x , y , x + 10 , y + 40);
			if (x > 400) { y += 60; x = 0; }
			SelectObject(hdc , GetStockObject(WHITE_BRUSH));
			DeleteObject(hBrush);
		}
		EndPaint(hWnd ,&ps);
		return 0;
	
	}
	return DefWindowProc(hWnd , msg , wp , lp);
}

int WINAPI WinMain(HINSTANCE hInstance , HINSTANCE hPrevInstance ,
			PSTR lpCmdLine , int nCmdShow) {
	HWND hWnd;
	MSG msg;
	WNDCLASS winc;

	winc.style 		= CS_HREDRAW | CS_VREDRAW;
	winc.lpfnWndProc	= WndProc;
	winc.cbClsExtra	= winc.cbWndExtra	= 0;
	winc.hInstance		= hInstance;
	winc.hIcon		= LoadIcon(NULL , IDI_APPLICATION);
	winc.hCursor		= LoadCursor(NULL , IDC_ARROW);
	winc.hbrBackground	= (HBRUSH)GetStockObject(WHITE_BRUSH);
	winc.lpszMenuName	= NULL;
	winc.lpszClassName	= TEXT("KITTY");

	if (!RegisterClass(&winc)) return 1;

	hWnd = CreateWindow(
			TEXT("KITTY") , TEXT("Kitty on your lap") ,
			WS_OVERLAPPEDWINDOW | WS_VISIBLE ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			CW_USEDEFAULT , CW_USEDEFAULT ,
			NULL , NULL , hInstance , NULL
	);

	if (hWnd == NULL) return 1;

	while (GetMessage(&msg , NULL , 0 , 0 )) {
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}

	return msg.wParam;
}
これは、先ほどのプログラムを PALETTEINDEX() マクロを使って改良したものです
近似色の検索が不要になるため、負荷も軽減されるので
あらかじめパレットに依存したプログラムを書く場合は、この方法が望まれます


CreatePalette()

HPALETTE CreatePalette(CONST LOGPALETTE *lplgpl);

論理パレットを作成します

lplgpl - LOGPALETTE 構造体へのポインタを指定します

戻り値 - 論理パレットのハンドル。失敗すると NULL

SelectPalette()

HPALETTE SelectPalette(
	HDC hdc,
	HPALETTE hpal,
	BOOL bForceBackground
);
デバイスコンテキストに論理パレットを選択します

hdc - デバイスコンテキストのハンドルを指定します
hpal - 論理パレットのハンドルを指定します
bForceBackground - TRUE を指定するとバックグラウンドパレットになります

戻り値 - 以前の論理パレットのハンドル、失敗すれば NULL

RealizePalette()

UINT RealizePalette(HDC hdc);

パレットエントリを、論理パレットからシステムパレットにマップします

hdc - 論理パレットが選択されているデバイスコンテキストのハンドルを指定します

戻り値 - システムにセットされた論理パレットのエントリ数、失敗すると GDI_ERROR

PALETTERGB()

COLORREF PALETTERGB(BYTE bRed , BYTE bGreen , BYTE bBlue);

論理パレットが設定されている時
システムが指定した RGB 値に最も近いパレットを参照する数値情報を返します
パレットが有効ではない場合、RGB 値をそのまま用います

bRed - 赤色の要素を表す 0 〜 0xFF までの数を指定します
bGreen - 緑色の要素を表す 0 〜 0xFF までの数を指定します
bBlue - 緑色の要素を表す 0 〜 0xFF までの数を指定します

戻り値 - パレットの参照を要求する RGB 値

PALETTEINDEX()

COLORREF PALETTEINDEX(WORD wPaletteIndex);

指定した数値を、論理パレットのインデックスを表すCOLORREF 型の数値に変換します

wPaletteIndex - 論理パレットのインデックス番号を指定します

戻り値 - 論理パレットのインデックスを参照する COLORREF 型の数値を返します



前のページへ戻る次のページへ