
Windows 上のコンソールで文字色や背景色を変更するために、 Windows API で直接色などの制御をするのではなく、 C言語の printf 関数などを通じた文字列の出力で、エスケープシーケンスを使用する方法を 確認していきます!
エスケープシーケンスを使うことで、文字を表示する位置や色を文字列で制御できるようになります。
Windows API の SetConsoleTextAttribute 関数を使えば、現在の文字の色を変更できますが、 エスケープシーケンスを使うことで、より良いメリットがあります。
Windows10 のコンソール画面(cmd.exe)上では、 エスケープシーケンスの機能が標準では OFF となっています。
これを ON にするためには、まず Windows API の ConsoleMode 関数 を呼び出して機能を ON にしてあげます。
エスケープシーケンスを使った簡単な例をみてみましょう!
#include <stdio.h>
#include <windows.h>
int main() {
HANDLE stdOut = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD consoleMode = 0;
GetConsoleMode(stdOut, &consoleMode);
consoleMode = consoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
SetConsoleMode(stdOut, consoleMode);
// 色を赤色に変更
printf("\x1B[31;1mここは赤色!\x1B[37;m");
printf("通常の白色");
}
これを実行すると下のような結果になります。
ちゃんと「ここは赤色!」という文字が赤い色になってますね。
Windows 上でコンソールアプリを作成するとき、 出力する先のコンソールはコンソールモードを持っています。
このコンソールモードを変更することで文字の出力についてのいくつかの基本的動作の制御ができます。
コンソールモードで代表的なものとして、以下のようなフラグを持っています
モード | 定数 | 概要 | デフォルト |
---|---|---|---|
ENABLE_PROCESSED_OUTPUT | 0x0001 | ASCII制御シーケンスを有効にする。例:「\n」なら改行する、など | 有効 |
ENABLE_WRAP_AT_EOL_OUTPUT | 0x0002 | 行の最後まで文字を表示した時、次の行の頭に移動する | 有効 |
ENABLE_VIRTUAL_TERMINAL_PROCESSING | 0x0004 | VT100ターミナル等の制御コードを有効にする | 無効 |
DISABLE_NEWLINE_AUTO_RETURN | 0x0008 | "\n" での改行時に行頭への移動("\r")は行わない | 無効 |
デフォルト値については、MSVC(Microsoft Visual Community)2019 での例です。
エスケープシーケンスは ENABLE_VIRTUAL_TERMINAL_PROCESSING のフラグを ON にすることで利用できるようになります。
main 関数から確認していきます。
int main() {
HANDLE stdOut = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD consoleMode = 0;
GetConsoleMode(stdOut, &consoleMode);
5 行目の中で、Windows API の GetStdHandle 関数を使って、標準出力先(コンソール画面)への ハンドルを取得しています。 ここで取得したコンソール画面へのハンドルに対して、エスケープシーケンス機能をONにします。
6 行目で DWORD 型の変数 consoleMode を宣言・初期化しています。 この変数 consoleMode に現在のコンソールモードの値を取得します。
まずは現在のコンソールモードのフラグを取得します。
GetConsoleMode(stdOut, &consoleMode);
この時点で consoleMode には数値の 3 が入っています。 3 は 1 + 2 ですので、ENABLE_PROCESSED_OUTPUT と ENABLE_WRAP_AT_EOL_OUTPUT のフラグが有効ということです。
続いて、このフラグに対して、エスケープシーケンス機能を使うための ENABLE_VIRTUAL_TERMINAL_PROCESSING フラグを有効にしますので、 OR 演算で ENABLE_VIRTUAL_TERMINAL_PROCESSING を有効にします。
consoleMode = consoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING;
この consoleMode を現在のコンソールに設定します。
SetConsoleMode(stdOut, consoleMode);
これで色を変更するなどのエスケープシーケンスが使用できるようになりました。
次の行で実際に色を変更してみます。
// 色を赤色に変更
printf("\x1B[31;1mここは赤色!\x1B[37;m");
この文字列の中に「文字の色を赤に変更」、「文字の色を白に戻す」というエスケープシーケンス が含まれています。まずは「文字の色を赤に変更」する箇所から確認していきましょう。
printf に渡す文字列の先頭に、文字列が含まれています。 これがエスケープシーケンスです。
\x1B[31;1m
この文字列がコンソールに出力される時に、コンソール側の機能で出力する文字が赤色に変更されます。 以降、printf や puts 関数などで出力する文字は赤色に変更されます。
続いて、\x1B[37;m
という文字列がきています。
エスケープシーケンスの文法について確認しておきます。
エスケープシーケンスは「\x1b」で始まります。最初の ”\x” は、16進数で後の 2 文字を扱うという意味となります。 つまり、「\x1b」は、1バイトで、16進数の 1b という意味です。1b は 10 進数に直すと、数字の 27 です。
ASCII コード表を参照してみると、 27 は特殊コードのエスケープです。
つまり、エスケープシーケンスは ASCII コードでいう、エスケープ(27 = '\x1b')から始まってます。
どのような機能を実行したいかをエスケープ('\x1b')の後に続いて書いていきます。 どのような機能を実行したいかの指示を制御シーケンス(Control Sequence)と呼びます。
制御シーケンスは通常、"[" の記号でスタートします。
たとえば赤色に文字を変更したい!という制御シーケンスの場合、
\x1B[31;1m
という記載ですが、この「赤色に変えたい!」という命令は「31;1m」という部分で指示しています。
制御シーケンスの内側ですが、下のような記載となっています。
カンマ(";")区切りのパラメータ + 実行したい命令
具体的に、赤色に変えたい!という命令は下のような制御シーケンスです。
色 | 制御シーケンス |
---|---|
リセット | 0m |
黒 | 30m |
赤 | 31m |
緑 | 32m |
黄 | 33m |
青 | 34m |
マゼンダ | 35m |
シアン | 36m |
白 | 37m |
上記の8種類(30~37)は予めコンソールの基本色としてパレットという情報で決まっています。 パレットの色は変更もできますが、8色のみを扱うのは使いづらいため、 RGB を直接指定して変更する方法を確認していきます。
RGB の色合いを直接指定して、色を変更する方法を サンプルコードで確認していきましょう!
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <windows.h>
// Virtual Terminal機能を有効にする
void enableVT() {
HANDLE stdOut = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD consoleMode = 0;
GetConsoleMode(stdOut, &consoleMode);
SetConsoleMode(stdOut, consoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
}
// エスケープシーケンスを標準出力する
void escSeq(char const *controlSequence, char const function ) {
printf("\x1B[");
printf(controlSequence);
putchar(function);
}
// RGB を指定して文字の色を変更する
void setFontColorRGB( const unsigned char r
,const unsigned char g
,const unsigned char b ) {
char buf[64];
sprintf(buf, "38;2;%d;%d;%d", r, g, b);
escSeq(buf, 'm');
}
// 色情報をリセットする
void resetColor() {
escSeq("0", 'm');
}
int main() {
enableVT();
for (int r = 0; r < 255; r += 8) {
for (int g = 0 ; g < 255; g += 8 * (r+1)) {
for (int b = 0 ; b < 255; b += 8 * (g+1)) {
setFontColorRGB(r, g, b);
printf("■");
}
}
}
}
実行すると下のような結果となります。
様々な色の ■ が表示されました!
今回作成した関数を確認していきます。
// Virtual Terminal機能を有効にする
void enableVT() {
HANDLE stdOut = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD consoleMode = 0;
GetConsoleMode(stdOut, &consoleMode);
SetConsoleMode(stdOut, consoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
}
5行目からの enableVT 関数は、呼び出すと エスケープシーケンスを実行できるようになります。 これをプログラムの最初に呼び出せば、様々な色を表示できるようになります。
続いて、エスケープシーケンスを標準出力するための escSeq 関数です。
// エスケープシーケンスを標準出力する
void escSeq(char const *controlSequence, char const function ) {
printf("\x1B[");
printf(controlSequence);
putchar(function);
}
escSeq( 制御シーケンス文字列, 制御シーケンスの命令文 );
という形で呼び出して使います。
続いて、表示する文字の色を RGB 形式(赤色、緑色、青色)で指定できる関数を確認します。
// RGB を指定して文字の色を変更する
void setFontColorRGB( const unsigned char r
,const unsigned char g
,const unsigned char b ) {
char buf[64];
sprintf(buf, "38;2;%d;%d;%d", r, g, b);
escSeq(buf, 'm');
}
この setFontColorRGB は下のように呼び出します。
setFontColorRGB( 赤, 緑, 青);
赤(red)、緑(green)、青(blue)は 0~255 の間で色の強さを指定します。
例えば、setFontColorRGB(0,0,0)
と呼び出しをすると、表示する文字は RGB(0,0,0) つまり黒色となります。
RGB を指定するエスケープコードを一つの printf で書くと下のようになります。
printf("\x1B[38;2;100;77;111m色が変わりました。");
この例では、R=100, G=77, B=111 を指定しています。
続いて、色の変更などをすべてリセットする関数を確認します。
// 色情報をリセットする
void resetColor() {
escSeq("0", 'm');
}
この resetColor を呼び出すと、変更した色などのフォント設定や背景色の設定が リセットされます。
最後に main 関数の中です。
int main() {
enableVT();
for (int r = 0; r < 255; r += 8) {
for (int g = 0 ; g < 255; g += 8 * (r+1)) {
for (int b = 0 ; b < 255; b += 8 * (g+1)) {
setFontColorRGB(r, g, b);
printf("■");
}
}
}
}
35行目で enableVT 関数を呼び出し、エスケープシーケンスを実行できるように コンソールの設定を変更しています。
36~43 行で、setFontColorRGB の R,G,B のそれぞれの値を 調整しながらループで呼び出ししています。 これによリカラフルな結果が出力されました。
エスケープシーケンスは画面の文字色を変更するだけではなく、 文字を出力する位置や、カーソル(画面上で点滅している、文字の出力位置)の表示を変更することが出来ます。
代表的なものを下に記載します。
制御シーケンス例 | 効果 |
---|---|
nA | カーソルを n 文字ぶん上に移動 |
nB | カーソルを n 文字ぶん下に移動 |
nC | カーソルを n 文字ぶん右に移動 |
nD | カーソルを n 文字ぶん左に移動 |
nE | カーソルを n 個ぶん下の最も左に移動 |
n;mH | カーソルを (m,n) の位置に移動 |
1J | コンソールに表示されている内容を消去する |
48;2;r;g;b | 背景色を r,g,b に変更する。 rgb はそれぞれ 0~255 |
?25h | カーソルの点滅を表示 |
?25l | カーソルの点滅を非表示 |
4m | 文字にアンダーラインを描画する |
ご参考に、コンソールアプリやコンソールゲームで、上記の色変更、カーソル移動の機能を使いやすくするために、 関数化しておきます。
// Virtual Terminal機能を有効にする
void enableVT() {
HANDLE stdOut = GetStdHandle(STD_OUTPUT_HANDLE);
DWORD consoleMode = 0;
GetConsoleMode(stdOut, &consoleMode);
SetConsoleMode(stdOut, consoleMode | ENABLE_VIRTUAL_TERMINAL_PROCESSING);
}
// エスケープシーケンスを標準出力する
void escSeq(char const *controlSequence, char const function ) {
printf("\x1B[");
printf(controlSequence);
putchar(function);
}
// RGB を指定して文字の色を変更する
void setFontColorRGB( const unsigned char r
,const unsigned char g
,const unsigned char b ) {
char buf[64];
sprintf(buf, "38;2;%d;%d;%d", r, g, b);
escSeq(buf, 'm');
}
void resetColor() { // 色情報をリセット
escSeq("0", 'm');
}
void setFontUnderline() { // アンダーライン
escSeq("4", 'm');
}
void moveCursorUp(const unsigned char movecount) { // 上に移動
char buf[4];
sprintf(buf, "%d", movecount);
escSeq(buf, 'A');
}
void moveCursorDown(const unsigned char movecount) { // 下に移動
char buf[4];
sprintf(buf, "%d", movecount);
escSeq(buf, 'B');
}
void moveCursorLeft(const unsigned char movecount) { // 左に移動
char buf[4];
sprintf(buf, "%d", movecount);
escSeq(buf, 'D');
}
void moveCursorRight(const unsigned char movecount) { // 右に移動
char buf[4];
sprintf(buf, "%d", movecount);
escSeq(buf, 'C');
}
void setCursorPosition( const unsigned char x
,const unsigned char y ) { // 指定位置に移動
char buf[4];
sprintf(buf, "%d;%d", y, x);
escSeq(buf, 'H');
}
void setCursorReturn() { // 改行同様に移動
escSeq("1", 'E');
}
void clearScreen() { // 画面表示をクリア
escSeq("1", 'J');
}
void showCursor() { // カーソル位置の点滅を表示
escSeq("?25", 'h');
}
void hideCursor() { // カーソル位置の点滅を非表示
escSeq("?25", 'l');
}
今回の使用したエスケープシーケンスは、元々DEC社(ディジタル・イクイップメント・コーポレーション)で 開発されたカラフルな文字表示ができる画面表示用の機器である VT100 で使われていた仕様です。
非常に使い勝手が良かったため、この VT100 のエスケープシーケンス機能を使った、エミュレータが Windows や Linux 上のコンソール上でも使えるようになっています。
コメント、ありがとうございます。
ごめんなさい。エラーでうまく送信できませんでした。ご迷惑をおかけします。しばらくおいてから再度送信を試していただくか、以下から DM などでご連絡頂ければと思います。
Twitter:@NodachiSoft_jpお名前:以下の内容でコメントを送信します。よろしければ、「送信」を押してください。修正する場合は「戻る」を押してください
お名前: