エディタを作る-1の4 APIで文字色 制御

せっかく エスケープ シーケンスが Windowsのコマンドプロンプトでも使えるようになる ANSICON という優秀なソフトを見つけたのに、Visual Studioのデバッグ出力画面で使えないという 「大どんでん返し」を食らった。

コマンドラインで使える colorコマンドも 目的としている用途とは 違っているし、動作が安定しない。  仕方ないので、素直にWindowsのAPIを使って、文字色と背景色を変えるコードを書く事にした。

 とりあえず移植性は考えずに、WindowsのVisual Studioでビルドできるコードを書き、後々 Mac OSや Linuxでも そのままソースが使えるように、後で改変する事とする。

 その 後の移植が少しでも楽になる事を考慮して、この文字色を変えるコードは(他のコンソールアプリでも使えそうなので)、別ファイルに分けて作成する事にする。
 複数ファイルでの 分割開発 の良いサンプルにもなると思う。


1)ファイルの構成

ファイルの構成は、次の通り。
 ① main.c ・・・ 今回はテストプログラムを入れる。
          文字色変更の関数を 利用する側のコード。

 ② color.c ・・・ 文字色変更 関連の関数が入るファイル。
 ③ color.h ・・・ 上のファイル color.c のヘッダーファイル
          (②③はペア)

作成した関数は、3つ (この関数名は当方で命名したもの)
 ① clearScreen()       ・・・ コンソール画面を消去して、
           カーソルを画面左上に移動させる
 ② textColor( 色コード )    ・・・ 文字の色を変更する
 ③ setColor(文字色, 背景色)  ・・・ 文字と背景色を変更
    (文字色、背景色 共に同じ色コードを使う)

文字だけの色を変える関数名を color() としてもいいのだが、将来 グラフィックの色を変える時の関数名にcolor() を使いたくなることもあるだろう。 あまりに汎用的でシンプルな単語は避けた方がいいだろうから、textColor() としました。
 これも colorText() と すべきか? textColor() とすべきか? 迷うところですが LATEXを見習って textColor としました。 (この方が自然かなと…)

C言語ではなく、C++なら(拡張子が .Cppなら)、②③とも同じ関数名 setColor() にして(引数の違いで区別できる)しまうことも出来るが、C言語では 同じ関数名は使えないので、このような別名に分けました。


では、実際のコードを見て行こう。

まずは、ヘッダーファイル: color.h の中身 ↓ から、

 # ifndef _COLOR_H_
 # define _COLOR_H_

 # include <windows.h>	//system()に必要

 //文字色の定義

 # define  T_黒        0x00
 # define  T_暗い青    0x01
 # define  T_暗い緑    0x02
 # define  T_暗い水色  0x03
 # define  T_暗い赤    0x04
 # define  T_暗い紫    0x05
 # define  T_暗い黄色  0x06
 # define  T_暗い白    0x07
 # define  T_灰色      0x08
 # define  T_青        0x09
 # define  T_緑        0x0a
 # define  T_水色      0x0b
 # define  T_赤        0x0c
 # define  T_紫        0x0d
 # define  T_黄色      0x0e
 # define  T_白        0x0f
 //
 # define  T_高輝度    0x08    // 高輝度マスクbit
 # define  T_赤_MASK   0x04    // 赤色マスクビット
 # define  T_緑_MASK   0x02    // 緑色マスクビット
 # define  T_青_MASK   0x01    // 青色マスクビット

// 画面を消去
void clearScreen(void);

//	文字色の変更
void textColor(int col);
void setColor(int fg, int bg);	//	文字色、背景色の変更

# endif	 //End of color.h

#define で 色の固定値 定義をここで行っている。(頭の T_ はテキスト(Text)の T のつもり)

2)色コード

ヘッダーファイルの中で 色コードを define宣言しているのだが、この命名も悩ましい所だ。

ネットで探し当てた (APIを使って) 文字に色を付けられる 色の定数の一例は、次の通り。 ただ、これも色々な命名例があって混乱している。

画像1

コマンド プロンプトで使えるcolorコマンドのヘルプ ↓ を表示させてみた。
上の例と このヘルプでの表現がすでに異なる。 ヘルプの方を 標準として これに従うのも 一つの手だが、「輝く白」などという変な表現もあるので使いづらい。

画像4

悩んだあげく、以下 ↓ の表現を使うことにした。 1~7の下位番号を「青~白」にするのではなく、「暗い~」という表現にし、8~Fの明るい色をシンプルな色名(灰色、青~白)とした。 (明るい~ という表現を無くした)

色コード

 define宣言による色コードも 英語を使うのが一般的だが、私は積極的に日本語(全角漢字)をそのまま使うことにしている。 この方が 日本人には可読性が上がると思う。
 この色コードの命名方法に問題があるようなら、英単語の color code を自分で定義して 使えば良い。


3)API プログラムの解説

次に color.c ↓ の関数定義 部分。

/*++
*
* Module Name:		Color.c
*
--*/
# include "color.h"

	WORD	gw背景色 = 0;		
	HANDLE				 hStdHandle = NULL;
# define GET_STDHANDLE() hStdHandle = GetStdHandle(STD_OUTPUT_HANDLE);
	
//--- 画面を消去
void	clearScreen(void)
{
# if defined(_WIN32) || defined(_WIN64)
	system("cls");
	GET_STDHANDLE();
# else
	printf("\x1B[2J\x1B[1;1H");		//画面をクリアし、キャレット位置を左上にx,y=1,1
# endif
}

//--- 文字色の変更
void	textColor(int col)
{
	if (hStdHandle != NULL){
		if (col <= 0x0F){
			col |= gw背景色;
			SetConsoleTextAttribute(hStdHandle, col);
		}
	}
}

//--- 文字と背景色の変更
void	setColor(int fg, int bg)
{
	WORD attr = 0;
	if (hStdHandle == NULL){
		GET_STDHANDLE();
	}
	//背景
	if (bg & T_高輝度) {
		attr |= BACKGROUND_INTENSITY;
	}
	if (bg & T_赤_MASK) {
		attr |= BACKGROUND_RED;
	}
	if (bg & T_緑_MASK) {
		attr |= BACKGROUND_GREEN;
	}
	if (bg & T_青_MASK) {
		attr |= BACKGROUND_BLUE;
	}
	gw背景色 = attr;   //一時記憶

	//文字
	if (fg & T_高輝度) {
		attr |= FOREGROUND_INTENSITY;
	}
	if (fg & T_赤_MASK) {
		attr |= FOREGROUND_RED;
	}
	if (fg & T_緑_MASK) {
		attr |= FOREGROUND_GREEN;
	}
	if (fg & T_青_MASK) {
		attr |= FOREGROUND_BLUE;
	}

	SetConsoleTextAttribute(hStdHandle, attr);
}
//--- End of Color.c

「画面の消去」clearScreen()関数だけは、開発環境が Windowsの場合、
  system("cls");  を実行し、
Mac-OS や Linux の場合、これまで通り エスケープシーケンスを使うよう移植性を保ってある。
 そのために、_WIN32 もしくは _WIN64 が定義されたいたら Windowsだと#if definedで 判断している。

textColo()関数や setColor()関数は、まったく移植性を考慮していないので、今後の課題とする。

使っているAPI 関数の解説:
① GetStdHandle(STD_OUTPUT_HANDLE); で 画面出力のハンドルを取得する。
② SetConsoleTextAttribute(ハンドル, attr)関数で 色を変更する
 ・ ハンドルの所には①で得た ハンドル値をセット
 ・attrの部分に 文字色の値をセットする
  (背景色も この attr にセットする)


4)動作確認 と 使い方のサンプル

textColor()関数を使う前に、必ず clearScreen() か setColor()を1回は実行しないといけません。 (ハンドルを取得するために)
 main.c 内の 動作テストの為のコード が 以下の通り。

/*	         ソリューション名     プロジェクト
 * main.cpp	(5_colorSet → colorSet)
 *
 *	画面消去&画面の文字に色を付ける。 APIで制御
 */

# include "color.h"
extern gw背景色;	//グローバル変数:背景色にアクセスできるようにする

# include <stdio.h>
# include <conio.h>

int		main(void)
{
	clearScreen();

	setColor(T_赤, T_灰色);		printf("赤,   BG:GRAY\n");
	setColor(T_黒, T_白);		printf("黒 ,   BG:WHITE\n");
	setColor(T_暗い青, T_白);	printf("暗い青 BG:WHITE\n");
	setColor(T_暗い緑, T_白);	printf("暗い緑 BG:WHITE\n");
	setColor(T_暗い水色, T_白);	printf("暗い水色 BG:WHITE\n");
	setColor(T_暗い赤, T_白);	printf("暗い赤 BG:WHITE\n");
	setColor(T_暗い紫, T_白);	printf("暗い紫 BG:WHITE\n");
	setColor(T_暗い黄色, T_白);	printf("暗い黄色 BG:WHITE\n");
	setColor(T_暗い白, T_白);	printf("暗い白 BG:WHITE\n");

	textColor(T_灰色);			printf("glay\n"); //背景色=白は引き継がれる
	textColor(T_青);			printf("blue\n");
	gw背景色 = T_黒; //← 背景色を強制的に黒に!
	textColor(T_緑);			printf("green\n");
	textColor(T_水色);			printf("cyan\n");
	textColor(T_赤);			printf("red\n");
	textColor(T_緑);			printf("green\n");
	textColor(T_黄色);			printf("yellow\n");
	textColor(T_白);			printf("white\n");

	setColor(T_白, T_黒);		printf("BG: black\n");
	printf("文字色=白\n");

	int i = getchar();	//キー入力 待ち
	return 0;
}

これを実行すると、↓ こんな画面になる。

画像5


 参考まで、
 背景色と文字色の組み合わせ一覧は、次の通り。

color-色の組み合わせ


ハッシュタグ
#C言語 #エディタを作る

この記事が気に入ったらサポートをしてみませんか?